lambda/rapi/handler/agentregister.go (174 lines of code) (raw):
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package handler
import (
"encoding/json"
"errors"
"io"
"net/http"
"strings"
log "github.com/sirupsen/logrus"
"go.amzn.com/lambda/core"
"go.amzn.com/lambda/rapi/model"
"go.amzn.com/lambda/rapi/rendering"
)
type agentRegisterHandler struct {
registrationService core.RegistrationService
}
// RegisterRequest represent /extension/register JSON body
type RegisterRequest struct {
Events []core.Event `json:"events"`
}
const featuresHeader = "Lambda-Extension-Accept-Feature"
type registrationFeature int
const (
accountFeature registrationFeature = iota + 1
)
var allowedFeatures = map[string]registrationFeature{
"accountId": accountFeature,
}
type responseModifier func(*model.ExtensionRegisterResponse)
func parseRegister(request *http.Request) (*RegisterRequest, error) {
body, err := io.ReadAll(request.Body)
if err != nil {
return nil, err
}
req := struct {
RegisterRequest
ConfigurationKeys []string `json:"configurationKeys"`
}{}
if err := json.Unmarshal(body, &req); err != nil {
return nil, err
}
if len(req.ConfigurationKeys) != 0 {
return nil, errors.New("configurationKeys are deprecated; use environment variables instead")
}
return &req.RegisterRequest, nil
}
func (h *agentRegisterHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
agentName := request.Header.Get(LambdaAgentName)
if agentName == "" {
rendering.RenderForbiddenWithTypeMsg(writer, request, errAgentNameInvalid, "Empty extension name")
return
}
var responseModifiers []responseModifier
for _, f := range parseRegistrationFeatures(request) {
if f == accountFeature {
responseModifiers = append(responseModifiers, h.respondWithAccountID())
}
}
registerRequest, err := parseRegister(request)
if err != nil {
rendering.RenderForbiddenWithTypeMsg(writer, request, errInvalidRequestFormat, "%s", err.Error())
return
}
agent, found := h.registrationService.FindExternalAgentByName(agentName)
if found {
h.registerExternalAgent(agent, registerRequest, writer, request, responseModifiers...)
} else {
h.registerInternalAgent(agentName, registerRequest, writer, request, responseModifiers...)
}
}
func (h *agentRegisterHandler) respondWithAccountID() responseModifier {
return func(resp *model.ExtensionRegisterResponse) {
resp.AccountID = h.registrationService.GetFunctionMetadata().AccountID
}
}
func parseRegistrationFeatures(request *http.Request) []registrationFeature {
rawFeatures := strings.Split(request.Header.Get(featuresHeader), ",")
var features []registrationFeature
for _, feature := range rawFeatures {
feature = strings.TrimSpace(feature)
if v, found := allowedFeatures[feature]; found {
features = append(features, v)
}
}
return features
}
func (h *agentRegisterHandler) renderResponse(
agentID string,
writer http.ResponseWriter,
request *http.Request,
respModifiers ...responseModifier,
) {
writer.Header().Set(LambdaAgentIdentifier, agentID)
metadata := h.registrationService.GetFunctionMetadata()
resp := &model.ExtensionRegisterResponse{
FunctionVersion: metadata.FunctionVersion,
FunctionName: metadata.FunctionName,
Handler: metadata.Handler,
}
for _, mod := range respModifiers {
mod(resp)
}
if err := rendering.RenderJSON(http.StatusOK, writer, request, resp); err != nil {
log.WithError(err).Warn("Error while rendering response")
http.Error(writer, err.Error(), http.StatusInternalServerError)
}
}
func (h *agentRegisterHandler) registerExternalAgent(
agent *core.ExternalAgent,
registerRequest *RegisterRequest,
writer http.ResponseWriter,
request *http.Request,
respModifiers ...responseModifier,
) {
for _, e := range registerRequest.Events {
if err := core.ValidateExternalAgentEvent(e); err != nil {
log.Warnf("Failed to register %s: event %s: %s", agent.Name, e, err)
rendering.RenderForbiddenWithTypeMsg(writer, request, errInvalidEventType, "%s: %s", e, err)
return
}
}
if err := agent.Register(registerRequest.Events); err != nil {
log.Warnf("Failed to register %s: %s", agent.String(), err)
rendering.RenderForbiddenWithTypeMsg(writer, request, errAgentInvalidState, StateTransitionFailedForExtensionMessageFormat,
agent.GetState().Name(), core.AgentRegisteredStateName, agent.Name, err)
return
}
h.renderResponse(agent.ID.String(), writer, request, respModifiers...)
log.Infof("External agent %s registered, subscribed to %v", agent.String(), registerRequest.Events)
}
func (h *agentRegisterHandler) registerInternalAgent(
agentName string,
registerRequest *RegisterRequest,
writer http.ResponseWriter,
request *http.Request,
respModifiers ...responseModifier,
) {
for _, e := range registerRequest.Events {
if err := core.ValidateInternalAgentEvent(e); err != nil {
log.Warnf("Failed to register %s: event %s: %s", agentName, e, err)
rendering.RenderForbiddenWithTypeMsg(writer, request, errInvalidEventType, "%s: %s", e, err)
return
}
}
agent, err := h.registrationService.CreateInternalAgent(agentName)
if err != nil {
log.Warnf("Failed to create internal agent %s: %s", agentName, err)
switch err {
case core.ErrRegistrationServiceOff:
rendering.RenderForbiddenWithTypeMsg(writer, request,
errAgentRegistrationClosed, "Extension registration closed already")
case core.ErrAgentNameCollision:
rendering.RenderForbiddenWithTypeMsg(writer, request,
errAgentInvalidState, "Extension with this name already registered")
case core.ErrTooManyExtensions:
rendering.RenderForbiddenWithTypeMsg(writer, request,
errTooManyExtensions, "Extension limit (%d) reached", core.MaxAgentsAllowed)
default:
rendering.RenderInternalServerError(writer, request)
}
return
}
if err := agent.Register(registerRequest.Events); err != nil {
log.Warnf("Failed to register %s: %s", agent.String(), err)
rendering.RenderForbiddenWithTypeMsg(writer, request, errAgentInvalidState, StateTransitionFailedForExtensionMessageFormat,
agent.GetState().Name(), core.AgentRegisteredStateName, agent.Name, err)
return
}
h.renderResponse(agent.ID.String(), writer, request, respModifiers...)
log.Infof("Internal agent %s registered, subscribed to %v", agent.String(), registerRequest.Events)
}
// NewAgentRegisterHandler returns a new instance of http handler for serving /extension/register
func NewAgentRegisterHandler(registrationService core.RegistrationService) http.Handler {
return &agentRegisterHandler{
registrationService: registrationService,
}
}