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 }