ecs-agent/introspection/v1/handlers/handlers.go (138 lines of code) (raw):
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may
// not use this file except in compliance with the License. A copy of the
// License is located at
//
// http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file 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 handlers
import (
"errors"
"fmt"
"net/http"
v1 "github.com/aws/amazon-ecs-agent/ecs-agent/introspection/v1"
"github.com/aws/amazon-ecs-agent/ecs-agent/logger"
"github.com/aws/amazon-ecs-agent/ecs-agent/logger/field"
"github.com/aws/amazon-ecs-agent/ecs-agent/metrics"
tmdsutils "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils"
)
const (
dockerIDQueryField = "dockerid"
taskARNQueryField = "taskarn"
dockerShortIDLen = 12
requestTypeAgent = "introspection/agent"
requestTypeTasks = "introspection/tasks"
V1AgentMetadataPath = "/v1/metadata"
V1TasksMetadataPath = "/v1/tasks"
)
// getHTTPErrorCode returns an appropriate HTTP response status code and metric name for a given error.
func getHTTPErrorCode(err error) (int, string) {
// Multiple tasks were found, but we expect only one
var errMultipleTasksFound *v1.ErrorMultipleTasksFound
if errors.As(err, &errMultipleTasksFound) {
return errMultipleTasksFound.StatusCode(), errMultipleTasksFound.MetricName()
}
// The requested object was not found
var errNotFound *v1.ErrorNotFound
if errors.As(err, &errNotFound) {
return errNotFound.StatusCode(), errNotFound.MetricName()
}
// There was an error finding the object, but we have some info to return
var errFetchFailure *v1.ErrorFetchFailure
if errors.As(err, &errFetchFailure) {
return errFetchFailure.StatusCode(), errFetchFailure.MetricName()
}
// Some unkown error has occurred
return http.StatusInternalServerError, metrics.IntrospectionInternalServerError
}
// defaultMetadataResponse returns an empty agent metadata response. The response will
// exclude the Version field if hideAgentVersion is true.
func defaultMetadataResponse(hideAgentVersion bool) any {
if hideAgentVersion {
return v1.UnversionedAgentMetadataResponse{}
}
return v1.AgentMetadataResponse{}
}
// prepareAgentMetadata strips the Version field from the provided agentMetadata response
// if hideAgentVersion is true. If not, it will return the metadata unchanged.
func prepareAgentMetadata(agentMetadata *v1.AgentMetadataResponse, hideAgentVersion bool) any {
if hideAgentVersion {
return &v1.UnversionedAgentMetadataResponse{
Cluster: agentMetadata.Cluster,
ContainerInstanceArn: agentMetadata.ContainerInstanceArn,
}
}
return agentMetadata
}
// AgentMetadataHandler returns the HTTP handler function for handling agent metadata requests.
func AgentMetadataHandler(
agentState v1.AgentState,
metricsFactory metrics.EntryFactory,
hideAgentVersion bool,
) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
agentMetadata, err := agentState.GetAgentMetadata()
if err != nil {
logger.Error("Failed to get v1 agent metadata.", logger.Fields{
field.Error: err,
})
responseCode, metricName := getHTTPErrorCode(err)
metricsFactory.New(metricName).Done(err)
tmdsutils.WriteJSONResponse(w, responseCode, defaultMetadataResponse(hideAgentVersion), requestTypeAgent)
return
}
tmdsutils.WriteJSONResponse(w, http.StatusOK, prepareAgentMetadata(agentMetadata, hideAgentVersion), requestTypeAgent)
}
}
// TasksMetadataHandler returns the HTTP handler function for handling tasks metadata requests.
func TasksMetadataHandler(
agentState v1.AgentState,
metricsFactory metrics.EntryFactory,
) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
dockerID, dockerIDExists := tmdsutils.ValueFromRequest(r, dockerIDQueryField)
taskArn, taskARNExists := tmdsutils.ValueFromRequest(r, taskARNQueryField)
if dockerIDExists && taskARNExists {
errorMsg := fmt.Sprintf("request contains both %s and %s but expect at most one of these", dockerIDQueryField, taskARNQueryField)
logger.Error("Bad request for v1 task metadata.", logger.Fields{
field.Error: errorMsg,
})
metricsFactory.New(metrics.IntrospectionBadRequest).Done(errors.New(errorMsg))
tmdsutils.WriteJSONResponse(w, http.StatusBadRequest, v1.TaskResponse{}, requestTypeTasks)
return
}
if dockerIDExists {
// Find the task that has a container with a matching docker ID.
if len(dockerID) > dockerShortIDLen {
getTaskMetadata(agentState.GetTaskMetadataByID, dockerID, field.DockerId, metricsFactory, w)
return
} else {
getTaskMetadata(agentState.GetTaskMetadataByShortID, dockerID, field.DockerId, metricsFactory, w)
return
}
} else if taskARNExists {
// Find the task with a matching Arn.
getTaskMetadata(agentState.GetTaskMetadataByArn, taskArn, field.TaskARN, metricsFactory, w)
return
} else {
// Return all tasks.
getTasksMetadata(agentState, metricsFactory, w)
}
}
}
// getTasksMetadata writes a list of metadata for all tasks to the response
func getTasksMetadata(
agentState v1.AgentState,
metricsFactory metrics.EntryFactory,
w http.ResponseWriter,
) {
tasksMetadata, err := agentState.GetTasksMetadata()
if err != nil {
logger.Error("Failed to get v1 tasks metadata.", logger.Fields{
field.Error: err,
})
responseCode, metricName := getHTTPErrorCode(err)
metricsFactory.New(metricName).Done(err)
tmdsutils.WriteJSONResponse(w, responseCode, v1.TasksResponse{}, requestTypeTasks)
return
}
tmdsutils.WriteJSONResponse(w, http.StatusOK, tasksMetadata, requestTypeTasks)
}
func getTaskMetadata(
lookupFn func(key string) (*v1.TaskResponse, error),
key string,
keyType string,
metricsFactory metrics.EntryFactory,
w http.ResponseWriter,
) {
taskMetadata, err := lookupFn(key)
if err != nil {
logger.Error("Failed to get v1 task metadata.", logger.Fields{
field.Error: err,
keyType: key,
})
responseCode, metricName := getHTTPErrorCode(err)
metricsFactory.New(metricName).Done(err)
tmdsutils.WriteJSONResponse(w, responseCode, v1.TaskResponse{}, requestTypeTasks)
return
}
tmdsutils.WriteJSONResponse(w, http.StatusOK, taskMetadata, requestTypeTasks)
}