diff --git a/api/http/handler/endpoints/endpoint_status_inspect.go b/api/http/handler/endpointedge/endpoint_edgestatus_inspect.go similarity index 63% rename from api/http/handler/endpoints/endpoint_status_inspect.go rename to api/http/handler/endpointedge/endpoint_edgestatus_inspect.go index 1d32a6e15..bf4dd7dce 100644 --- a/api/http/handler/endpoints/endpoint_status_inspect.go +++ b/api/http/handler/endpointedge/endpoint_edgestatus_inspect.go @@ -1,8 +1,9 @@ -package endpoints +package endpointedge import ( "encoding/base64" "errors" + "fmt" "net/http" "strconv" "time" @@ -33,7 +34,7 @@ type edgeJobResponse struct { Version int `json:"Version" example:"2"` } -type endpointStatusInspectResponse struct { +type endpointEdgeStatusInspectResponse struct { // Status represents the environment(endpoint) status Status string `json:"status" example:"REQUIRED"` // The tunnel port @@ -43,26 +44,26 @@ type endpointStatusInspectResponse struct { // The current value of CheckinInterval CheckinInterval int `json:"checkin" example:"5"` // - Credentials string `json:"credentials" example:""` + Credentials string `json:"credentials"` // List of stacks to be deployed on the environments(endpoints) Stacks []stackStatusResponse `json:"stacks"` } -// @id EndpointStatusInspect +// @id EndpointEdgeStatusInspect // @summary Get environment(endpoint) status -// @description Environment(Endpoint) for edge agent to check status of environment(endpoint) +// @description environment(endpoint) for edge agent to check status of environment(endpoint) // @description **Access policy**: restricted only to Edge environments(endpoints) // @tags endpoints // @security ApiKeyAuth // @security jwt // @param id path int true "Environment(Endpoint) identifier" -// @success 200 {object} endpointStatusInspectResponse "Success" +// @success 200 {object} endpointEdgeStatusInspectResponse "Success" // @failure 400 "Invalid request" // @failure 403 "Permission denied to access environment(endpoint)" // @failure 404 "Environment(Endpoint) not found" // @failure 500 "Server error" -// @router /endpoints/{id}/status [get] -func (handler *Handler) endpointStatusInspect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { +// @router /endpoints/{id}/edge/status [get] +func (handler *Handler) endpointEdgeStatusInspect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") if err != nil { return &httperror.HandlerError{http.StatusBadRequest, "Invalid environment identifier route variable", err} @@ -84,32 +85,11 @@ func (handler *Handler) endpointStatusInspect(w http.ResponseWriter, r *http.Req edgeIdentifier := r.Header.Get(portainer.PortainerAgentEdgeIDHeader) endpoint.EdgeID = edgeIdentifier - agentPlatformHeader := r.Header.Get(portainer.HTTPResponseAgentPlatform) - if agentPlatformHeader == "" { - return &httperror.HandlerError{http.StatusInternalServerError, "Agent Platform Header is missing", errors.New("Agent Platform Header is missing")} + agentPlatform, agentPlatformErr := parseAgentPlatform(r) + if agentPlatformErr != nil { + return httperror.BadRequest("agent platform header is not valid", err) } - - agentPlatformNumber, err := strconv.Atoi(agentPlatformHeader) - if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to parse agent platform header", err} - } - - agentPlatform := portainer.AgentPlatform(agentPlatformNumber) - - if agentPlatform == portainer.AgentPlatformDocker { - endpoint.Type = portainer.EdgeAgentOnDockerEnvironment - } else if agentPlatform == portainer.AgentPlatformKubernetes { - endpoint.Type = portainer.EdgeAgentOnKubernetesEnvironment - } - } - - if endpoint.EdgeCheckinInterval == 0 { - settings, err := handler.DataStore.Settings().Settings() - if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve settings from the database", err} - } - - endpoint.EdgeCheckinInterval = settings.EdgeAgentCheckinInterval + endpoint.Type = agentPlatform } endpoint.LastCheckInDate = time.Now().Unix() @@ -119,50 +99,98 @@ func (handler *Handler) endpointStatusInspect(w http.ResponseWriter, r *http.Req return &httperror.HandlerError{http.StatusInternalServerError, "Unable to Unable to persist environment changes inside the database", err} } + checkinInterval := endpoint.EdgeCheckinInterval + if endpoint.EdgeCheckinInterval == 0 { + settings, err := handler.DataStore.Settings().Settings() + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve settings from the database", err} + } + checkinInterval = settings.EdgeAgentCheckinInterval + } + tunnel := handler.ReverseTunnelService.GetTunnelDetails(endpoint.ID) - schedules := []edgeJobResponse{} - for _, job := range tunnel.Jobs { - schedule := edgeJobResponse{ - ID: job.ID, - CronExpression: job.CronExpression, - CollectLogs: job.Endpoints[endpoint.ID].CollectLogs, - Version: job.Version, - } - - file, err := handler.FileService.GetFileContent(job.ScriptPath, "") - - if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve Edge job script file", err} - } - - schedule.Script = base64.RawStdEncoding.EncodeToString(file) - - schedules = append(schedules, schedule) - } - - statusResponse := endpointStatusInspectResponse{ + statusResponse := endpointEdgeStatusInspectResponse{ Status: tunnel.Status, Port: tunnel.Port, - Schedules: schedules, - CheckinInterval: endpoint.EdgeCheckinInterval, + CheckinInterval: checkinInterval, Credentials: tunnel.Credentials, } + schedules, handlerErr := handler.buildSchedules(endpoint.ID, tunnel) + if handlerErr != nil { + return handlerErr + } + statusResponse.Schedules = schedules + if tunnel.Status == portainer.EdgeAgentManagementRequired { handler.ReverseTunnelService.SetTunnelStatusToActive(endpoint.ID) } - relation, err := handler.DataStore.EndpointRelation().EndpointRelation(endpoint.ID) + edgeStacksStatus, handlerErr := handler.buildEdgeStacks(endpoint.ID) + if handlerErr != nil { + return handlerErr + } + statusResponse.Stacks = edgeStacksStatus + + return response.JSON(w, statusResponse) +} + +func parseAgentPlatform(r *http.Request) (portainer.EndpointType, error) { + agentPlatformHeader := r.Header.Get(portainer.HTTPResponseAgentPlatform) + if agentPlatformHeader == "" { + return 0, errors.New("agent platform header is missing") + } + + agentPlatformNumber, err := strconv.Atoi(agentPlatformHeader) if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve relation object from the database", err} + return 0, err + } + + agentPlatform := portainer.AgentPlatform(agentPlatformNumber) + + switch agentPlatform { + case portainer.AgentPlatformDocker: + return portainer.EdgeAgentOnDockerEnvironment, nil + case portainer.AgentPlatformKubernetes: + return portainer.EdgeAgentOnKubernetesEnvironment, nil + default: + return 0, fmt.Errorf("agent platform %v is not valid", agentPlatform) + } +} + +func (handler *Handler) buildSchedules(endpointID portainer.EndpointID, tunnel portainer.TunnelDetails) ([]edgeJobResponse, *httperror.HandlerError) { + schedules := []edgeJobResponse{} + for _, job := range tunnel.Jobs { + schedule := edgeJobResponse{ + ID: job.ID, + CronExpression: job.CronExpression, + CollectLogs: job.Endpoints[endpointID].CollectLogs, + Version: job.Version, + } + + file, err := handler.FileService.GetFileContent(job.ScriptPath, "") + if err != nil { + return nil, &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve Edge job script file", err} + } + schedule.Script = base64.RawStdEncoding.EncodeToString(file) + + schedules = append(schedules, schedule) + } + return schedules, nil +} + +func (handler *Handler) buildEdgeStacks(endpointID portainer.EndpointID) ([]stackStatusResponse, *httperror.HandlerError) { + relation, err := handler.DataStore.EndpointRelation().EndpointRelation(endpointID) + if err != nil { + return nil, &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve relation object from the database", err} } edgeStacksStatus := []stackStatusResponse{} for stackID := range relation.EdgeStacks { stack, err := handler.DataStore.EdgeStack().EdgeStack(stackID) if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve edge stack from the database", err} + return nil, &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve edge stack from the database", err} } stackStatus := stackStatusResponse{ @@ -172,8 +200,5 @@ func (handler *Handler) endpointStatusInspect(w http.ResponseWriter, r *http.Req edgeStacksStatus = append(edgeStacksStatus, stackStatus) } - - statusResponse.Stacks = edgeStacksStatus - - return response.JSON(w, statusResponse) + return edgeStacksStatus, nil } diff --git a/api/http/handler/endpointedge/handler.go b/api/http/handler/endpointedge/handler.go index 44543d6f4..3bee3d403 100644 --- a/api/http/handler/endpointedge/handler.go +++ b/api/http/handler/endpointedge/handler.go @@ -27,6 +27,8 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler { requestBouncer: bouncer, } + h.Handle("/{id}/edge/status", + bouncer.PublicAccess(httperror.LoggerHandler(h.endpointEdgeStatusInspect))).Methods(http.MethodGet) h.Handle("/{id}/edge/stacks/{stackId}", bouncer.PublicAccess(httperror.LoggerHandler(h.endpointEdgeStackInspect))).Methods(http.MethodGet) h.Handle("/{id}/edge/jobs/{jobID}/logs", diff --git a/api/http/handler/endpoints/handler.go b/api/http/handler/endpoints/handler.go index 2963933dc..99de0e2de 100644 --- a/api/http/handler/endpoints/handler.go +++ b/api/http/handler/endpoints/handler.go @@ -64,8 +64,6 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler { bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.endpointDockerhubStatus))).Methods(http.MethodGet) h.Handle("/endpoints/{id}/snapshot", bouncer.AdminAccess(httperror.LoggerHandler(h.endpointSnapshot))).Methods(http.MethodPost) - h.Handle("/endpoints/{id}/status", - bouncer.PublicAccess(httperror.LoggerHandler(h.endpointStatusInspect))).Methods(http.MethodGet) h.Handle("/endpoints/{id}/registries", bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.endpointRegistriesList))).Methods(http.MethodGet) h.Handle("/endpoints/{id}/registries/{registryId}", diff --git a/api/swagger.yaml b/api/swagger.yaml index 6ea6d03a2..240f939bb 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -470,7 +470,7 @@ definitions: example: 2 type: integer type: object - endpoints.endpointStatusInspectResponse: + endpoints.endpointEdgeStatusInspectResponse: properties: checkin: description: The current value of CheckinInterval @@ -4156,12 +4156,12 @@ paths: summary: Snapshots an endpoint tags: - endpoints - /endpoints/{id}/status: + /endpoints/{id}/edge/status: get: description: |- Endpoint for edge agent to check status of environment **Access policy**: restricted only to Edge endpoints - operationId: EndpointStatusInspect + operationId: EndpointEdgeStatusInspect parameters: - description: Endpoint identifier in: path @@ -4172,7 +4172,7 @@ paths: "200": description: Success schema: - $ref: '#/definitions/endpoints.endpointStatusInspectResponse' + $ref: '#/definitions/endpoints.endpointEdgeStatusInspectResponse' "400": description: Invalid request "403":