From 88782b0cbdf9f3f7f4f3c15900ba153e3cb7bf56 Mon Sep 17 00:00:00 2001 From: Jay Camp <jay.r.camp@gmail.com> Date: Fri, 26 Mar 2021 18:14:38 -0400 Subject: [PATCH] Remove k8s.cluster.name from eks detector (#2898) Turns out the cluster detection was not reliable as it doesn't work with a vanilla EKS cluster. Requires some sort of cloudwatch addon. Can't find a suitable replacement so removing the code. Users can still add it manually using OTEL_RESOURCE_ATTRIBUTES or some other resource modifying processor. --- .../internal/aws/eks/detector.go | 162 +----------------- .../internal/aws/eks/detector_test.go | 47 +---- 2 files changed, 14 insertions(+), 195 deletions(-) diff --git a/processor/resourcedetectionprocessor/internal/aws/eks/detector.go b/processor/resourcedetectionprocessor/internal/aws/eks/detector.go index 5ccee871517d..a71c8dc7a457 100644 --- a/processor/resourcedetectionprocessor/internal/aws/eks/detector.go +++ b/processor/resourcedetectionprocessor/internal/aws/eks/detector.go @@ -16,12 +16,6 @@ package eks import ( "context" - "crypto/tls" - "crypto/x509" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" "os" "go.opentelemetry.io/collector/component" @@ -35,55 +29,26 @@ const ( // TypeStr is type of detector. TypeStr = "eks" - /* #nosec */ - k8sTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token" - k8sCertPath = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" - authConfigmapPath = "https://kubernetes.default.svc/api/v1/namespaces/kube-system/configmaps/aws-auth" - cwConfigmapPath = "https://kubernetes.default.svc/api/v1/namespaces/amazon-cloudwatch/configmaps/cluster-info" + // Environment variable that is set when running on Kubernetes. + kubernetesServiceHostEnvVar = "KUBERNETES_SERVICE_HOST" ) -// detectorUtils is used for testing the resourceDetector by abstracting functions that rely on external systems. -type detectorUtils interface { - // fileExists returns true if the file exists, otherwise false. - fileExists(filename string) bool - // fetchString executes an HTTP request with a given HTTP Method and URL string returning - // the content body or an error. - fetchString(ctx context.Context, httpMethod string, URL string) (string, error) -} - -// This struct will implement the detectorUtils interface -type eksDetectorUtils struct{} - -// This struct will help unmarshal clustername from JSON response -type data struct { - ClusterName string `json:"cluster.name"` -} - var _ internal.Detector = (*Detector)(nil) // Detector for EKS -type Detector struct { - utils detectorUtils -} - -// Compile time assertion that eksDetectorUtils implements the detectorUtils interface. -var _ detectorUtils = (*eksDetectorUtils)(nil) +type Detector struct{} // NewDetector returns a resource detector that will detect AWS EKS resources. func NewDetector(_ component.ProcessorCreateParams, _ internal.DetectorConfig) (internal.Detector, error) { - return &Detector{utils: &eksDetectorUtils{}}, nil + return &Detector{}, nil } // Detect returns a Resource describing the Amazon EKS environment being run in. func (detector *Detector) Detect(ctx context.Context) (pdata.Resource, error) { res := pdata.NewResource() - isEks, err := isEKS(ctx, detector.utils) - if err != nil { - return res, err - } - // Return empty resource object if not running in EKS - if !isEks { + // Check if running on k8s. + if os.Getenv(kubernetesServiceHostEnvVar) == "" { return res, nil } @@ -91,120 +56,5 @@ func (detector *Detector) Detect(ctx context.Context) (pdata.Resource, error) { attr.InsertString(conventions.AttributeCloudProvider, conventions.AttributeCloudProviderAWS) attr.InsertString(conventions.AttributeCloudPlatform, conventions.AttributeCloudPlatformAWSEKS) - // Get clusterName and append to attributes - clusterName, err := getClusterName(ctx, detector.utils) - if err != nil { - return res, err - } - if clusterName != "" { - attr.InsertString(conventions.AttributeK8sCluster, clusterName) - } - return res, nil } - -// isEKS checks if the current environment is running in EKS. -func isEKS(ctx context.Context, utils detectorUtils) (bool, error) { - if !isK8s(utils) { - return false, nil - } - - // Make HTTP GET request - awsAuth, err := utils.fetchString(ctx, http.MethodGet, authConfigmapPath) - if err != nil { - return false, fmt.Errorf("isEks() error retrieving auth configmap: %w", err) - } - - return awsAuth != "", nil -} - -// isK8s checks if the current environment is running in a Kubernetes environment -func isK8s(utils detectorUtils) bool { - return utils.fileExists(k8sTokenPath) && utils.fileExists(k8sCertPath) -} - -// fileExists returns true if the file exists, otherwise false. -func (eksUtils eksDetectorUtils) fileExists(filename string) bool { - info, err := os.Stat(filename) - return err == nil && !info.IsDir() -} - -// fetchString executes an HTTP request with a given HTTP Method and URL string returning -// the content body or an error. -func (eksUtils eksDetectorUtils) fetchString(ctx context.Context, httpMethod string, URL string) (string, error) { - request, err := http.NewRequestWithContext(ctx, httpMethod, URL, nil) - if err != nil { - return "", fmt.Errorf("failed to create new HTTP request with method=%s, URL=%s: %w", httpMethod, URL, err) - } - - // Set HTTP request header with authentication credentials - k8sToken, err := getK8sToken() - if err != nil { - return "", err - } - request.Header.Set("Authorization", "Bearer "+k8sToken) - - // Get certificate - caCert, err := ioutil.ReadFile(k8sCertPath) - if err != nil { - return "", fmt.Errorf("failed to read file with path %s", k8sCertPath) - } - caCertPool := x509.NewCertPool() - caCertPool.AppendCertsFromPEM(caCert) - - // Set HTTP request timeout and add certificate - client := &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - RootCAs: caCertPool, - }, - }, - } - - response, err := client.Do(request) - if err != nil || response.StatusCode != http.StatusOK { - return "", fmt.Errorf("failed to execute HTTP request with method=%s, URL=%s, Status Code=%d: %w", httpMethod, URL, response.StatusCode, err) - } - - // Retrieve response body from HTTP request - body, err := ioutil.ReadAll(response.Body) - if err != nil { - return "", fmt.Errorf("failed to read response from HTTP request with method=%s, URL=%s: %w", httpMethod, URL, err) - } - - return string(body), nil -} - -// getK8sToken retrieves the kubernetes credential information. -func getK8sToken() (string, error) { - content, err := ioutil.ReadFile(k8sTokenPath) - if err != nil { - return "", fmt.Errorf("getK8sToken() error: cannot read file with path %s", k8sTokenPath) - } - - return string(content), nil -} - -// getClusterName retrieves the clusterName resource attribute -func getClusterName(ctx context.Context, utils detectorUtils) (string, error) { - resp, err := utils.fetchString(ctx, "GET", cwConfigmapPath) - if err != nil { - return "", fmt.Errorf("getClusterName() error: %w", err) - } - - // parse JSON object returned from HTTP request - var respmap map[string]json.RawMessage - err = json.Unmarshal([]byte(resp), &respmap) - if err != nil { - return "", fmt.Errorf("getClusterName() error: cannot parse JSON: %w", err) - } - var d data - err = json.Unmarshal(respmap["data"], &d) - if err != nil { - return "", fmt.Errorf("getClusterName() error: cannot parse JSON: %w", err) - } - - clusterName := d.ClusterName - - return clusterName, nil -} diff --git a/processor/resourcedetectionprocessor/internal/aws/eks/detector_test.go b/processor/resourcedetectionprocessor/internal/aws/eks/detector_test.go index cbcaca9d5abf..532dad437545 100644 --- a/processor/resourcedetectionprocessor/internal/aws/eks/detector_test.go +++ b/processor/resourcedetectionprocessor/internal/aws/eks/detector_test.go @@ -16,10 +16,10 @@ package eks import ( "context" + "os" "testing" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/component" "go.uber.org/zap" @@ -27,22 +27,6 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal" ) -type MockDetectorUtils struct { - mock.Mock -} - -// Mock function for fileExists() -func (detectorUtils *MockDetectorUtils) fileExists(filename string) bool { - args := detectorUtils.Called(filename) - return args.Bool(0) -} - -// Mock function for fetchString() -func (detectorUtils *MockDetectorUtils) fetchString(ctx context.Context, httpMethod string, URL string) (string, error) { - args := detectorUtils.Called(ctx, httpMethod, URL) - return args.String(0), args.Error(1) -} - func TestNewDetector(t *testing.T) { detector, err := NewDetector(component.ProcessorCreateParams{Logger: zap.NewNop()}, nil) assert.NoError(t, err) @@ -50,42 +34,27 @@ func TestNewDetector(t *testing.T) { } // Tests EKS resource detector running in EKS environment -func TestEks(t *testing.T) { - detectorUtils := &MockDetectorUtils{} +func TestEKS(t *testing.T) { ctx := context.Background() - // Mock functions and set expectations - detectorUtils.On("fileExists", k8sTokenPath).Return(true) - detectorUtils.On("fileExists", k8sCertPath).Return(true) - detectorUtils.On("fetchString", ctx, "GET", authConfigmapPath).Return("not empty", nil) - detectorUtils.On("fetchString", ctx, "GET", cwConfigmapPath).Return(`{"data":{"cluster.name":"my-cluster"}}`, nil) + require.NoError(t, os.Setenv("KUBERNETES_SERVICE_HOST", "localhost")) // Call EKS Resource detector to detect resources - eksResourceDetector := &Detector{utils: detectorUtils} + eksResourceDetector := &Detector{} res, err := eksResourceDetector.Detect(ctx) require.NoError(t, err) assert.Equal(t, map[string]interface{}{ - "cloud.provider": "aws", - "cloud.platform": "aws_eks", - "k8s.cluster.name": "my-cluster", + "cloud.provider": "aws", + "cloud.platform": "aws_eks", }, internal.AttributesToMap(res.Attributes()), "Resource object returned is incorrect") - detectorUtils.AssertExpectations(t) } // Tests EKS resource detector not running in EKS environment func TestNotEKS(t *testing.T) { - detectorUtils := new(MockDetectorUtils) - - /* #nosec */ - k8sTokenPath := "/var/run/secrets/kubernetes.io/serviceaccount/token" - - // Mock functions and set expectations - detectorUtils.On("fileExists", k8sTokenPath).Return(false) - - detector := Detector{detectorUtils} + detector := Detector{} + require.NoError(t, os.Unsetenv("KUBERNETES_SERVICE_HOST")) r, err := detector.Detect(context.Background()) require.NoError(t, err) assert.Equal(t, 0, r.Attributes().Len(), "Resource object should be empty") - detectorUtils.AssertExpectations(t) }