github.com/instana/go-sensor@v1.62.2-0.20240520081010-4919868049e1/aws/ecs_metadata.go (about)

     1  // (c) Copyright IBM Corp. 2021
     2  // (c) Copyright Instana Inc. 2020
     3  
     4  package aws
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"fmt"
    10  	"io"
    11  	"net/http"
    12  	"time"
    13  
    14  	"github.com/instana/go-sensor/docker"
    15  )
    16  
    17  // ContainerLimits represents the resource limits specified at the task level
    18  type ContainerLimits struct {
    19  	CPU    int `json:"CPU"`
    20  	Memory int `json:"Memory"`
    21  }
    22  
    23  // ContainerLabels represents AWS container labels
    24  type ContainerLabels struct {
    25  	Cluster               string `json:"com.amazonaws.ecs.cluster"`
    26  	TaskARN               string `json:"com.amazonaws.ecs.task-arn"`
    27  	TaskDefinition        string `json:"com.amazonaws.ecs.task-definition-family"`
    28  	TaskDefinitionVersion string `json:"com.amazonaws.ecs.task-definition-version"`
    29  }
    30  
    31  // ContainerNetwork represents AWS container network configuration
    32  type ContainerNetwork struct {
    33  	Mode          string   `json:"NetworkMode"`
    34  	IPv4Addresses []string `json:"IPv4Addresses"`
    35  }
    36  
    37  // ECSContainerMetadata represents the ECS container metadata as described in
    38  // https://docs.aws.amazon.com/AmazonECS/latest/developerguide/container-metadata.html#metadata-file-format
    39  type ECSContainerMetadata struct {
    40  	DockerID        string             `json:"DockerId"`
    41  	Name            string             `json:"Name"`
    42  	DockerName      string             `json:"DockerName"`
    43  	Image           string             `json:"Image"`
    44  	ImageID         string             `json:"ImageID"`
    45  	DesiredStatus   string             `json:"DesiredStatus"`
    46  	KnownStatus     string             `json:"KnownStatus"`
    47  	Limits          ContainerLimits    `json:"Limits"`
    48  	CreatedAt       time.Time          `json:"CreatedAt"`
    49  	StartedAt       time.Time          `json:"StartedAt"`
    50  	Type            string             `json:"Type"`
    51  	Networks        []ContainerNetwork `json:"Networks"`
    52  	ContainerLabels `json:"Labels"`
    53  }
    54  
    55  // ECSTaskMetadata represents the ECS task metadata as described in
    56  // https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-metadata-endpoint-v3.html#task-metadata-endpoint-v3-response
    57  type ECSTaskMetadata struct {
    58  	TaskARN          string                 `json:"TaskARN"`
    59  	AvailabilityZone string                 `json:"AvailabilityZone,omitempty"` // only available starting from ECS platform v1.4
    60  	Family           string                 `json:"Family"`
    61  	Revision         string                 `json:"Revision"`
    62  	DesiredStatus    string                 `json:"DesiredStatus"`
    63  	KnownStatus      string                 `json:"KnownStatus"`
    64  	Containers       []ECSContainerMetadata `json:"Containers"`
    65  	PullStartedAt    time.Time              `json:"PullStartedAt"`
    66  	PullStoppedAt    time.Time              `json:"PullStoppedAt"`
    67  }
    68  
    69  // ECSMetadataProvider retireves ECS service metadata from the ECS_CONTAINER_METADATA_URI endpoint
    70  type ECSMetadataProvider struct {
    71  	Endpoint string
    72  	client   *http.Client
    73  }
    74  
    75  // NewECSMetadataProvider initializes a new ECSMetadataClient with given endpoint and HTTP client.
    76  // If there is no HTTP client provided, the provider will use http.DefaultClient
    77  func NewECSMetadataProvider(endpoint string, c *http.Client) *ECSMetadataProvider {
    78  	if c == nil {
    79  		c = http.DefaultClient
    80  	}
    81  
    82  	return &ECSMetadataProvider{
    83  		Endpoint: endpoint,
    84  		client:   c,
    85  	}
    86  }
    87  
    88  // ContainerMetadata returns ECS metadata for current container
    89  func (c *ECSMetadataProvider) ContainerMetadata(ctx context.Context) (ECSContainerMetadata, error) {
    90  	var data ECSContainerMetadata
    91  
    92  	req, err := http.NewRequest(http.MethodGet, c.Endpoint, nil)
    93  	if err != nil {
    94  		return data, fmt.Errorf("failed to prepare request: %s", err)
    95  	}
    96  
    97  	body, err := c.executeRequest(req.WithContext(ctx))
    98  	if err != nil {
    99  		return data, fmt.Errorf("failed to fetch container metadata: %s", err)
   100  	}
   101  	defer body.Close()
   102  
   103  	if err := json.NewDecoder(body).Decode(&data); err != nil {
   104  		return data, fmt.Errorf("malformed container metadata response: %s", err)
   105  	}
   106  
   107  	return data, nil
   108  }
   109  
   110  // TaskMetadata returns ECS metadata for current task
   111  func (c *ECSMetadataProvider) TaskMetadata(ctx context.Context) (ECSTaskMetadata, error) {
   112  	var data ECSTaskMetadata
   113  
   114  	req, err := http.NewRequest(http.MethodGet, c.Endpoint+"/task", nil)
   115  	if err != nil {
   116  		return data, fmt.Errorf("failed to prepare request: %s", err)
   117  	}
   118  
   119  	body, err := c.executeRequest(req.WithContext(ctx))
   120  	if err != nil {
   121  		return data, fmt.Errorf("failed to fetch task metadata: %s", err)
   122  	}
   123  	defer body.Close()
   124  
   125  	if err := json.NewDecoder(body).Decode(&data); err != nil {
   126  		return data, fmt.Errorf("malformed task metadata response: %s", err)
   127  	}
   128  
   129  	return data, nil
   130  }
   131  
   132  // TaskStats returns Docker stats for current ECS task
   133  func (c *ECSMetadataProvider) TaskStats(ctx context.Context) (map[string]docker.ContainerStats, error) {
   134  	var data map[string]docker.ContainerStats
   135  
   136  	req, err := http.NewRequest(http.MethodGet, c.Endpoint+"/task/stats", nil)
   137  	if err != nil {
   138  		return data, fmt.Errorf("failed to prepare request: %s", err)
   139  	}
   140  
   141  	body, err := c.executeRequest(req.WithContext(ctx))
   142  	if err != nil {
   143  		return data, fmt.Errorf("failed to fetch task metadata: %s", err)
   144  	}
   145  	defer body.Close()
   146  
   147  	if err := json.NewDecoder(body).Decode(&data); err != nil {
   148  		return data, fmt.Errorf("malformed task stats response: %s", err)
   149  	}
   150  
   151  	return data, nil
   152  }
   153  
   154  func (c *ECSMetadataProvider) executeRequest(req *http.Request) (io.ReadCloser, error) {
   155  	resp, err := c.client.Do(req)
   156  	if err != nil {
   157  		return nil, fmt.Errorf("failed to execute request: %s", err)
   158  	}
   159  
   160  	if resp.StatusCode != http.StatusOK {
   161  		return nil, fmt.Errorf("the endpoint responded with %s", resp.Status)
   162  	}
   163  
   164  	return resp.Body, nil
   165  }