mirror of
https://github.com/portainer/portainer.git
synced 2025-07-22 06:49:40 +02:00
feat(agent): add auto agent platform detection (#4132)
* feat(endpoint): check endpoint type on creation * feat(edge): check edge endpoint type * feat(endpoint): send endpoint creation type * feat(endpoint): pass tls config * feat(endpoint): show connect errors * fix(endpoint): set correct endpoint type * feat(endpoint): support endpoint creation * style(endpoint): remove todo comment * feat(endpoint): set protocol for endpoint url * feat(endpoint): change scheme of url * fix(endpoint): toggle code block * feat(edge): report missing agent platform header * fix(api/endpoints): fix an issue with agent on kubernetes endpoint * feat(core/endpoints): minor UI update Co-authored-by: Anthony Lapenna <lapenna.anthony@gmail.com>
This commit is contained in:
parent
490b7ad26f
commit
bd7d7dcef5
11 changed files with 226 additions and 76 deletions
|
@ -2,17 +2,20 @@ package endpoints
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/crypto"
|
||||
"github.com/portainer/portainer/api/http/client"
|
||||
"github.com/portainer/portainer/api/internal/edge"
|
||||
)
|
||||
|
@ -20,7 +23,7 @@ import (
|
|||
type endpointCreatePayload struct {
|
||||
Name string
|
||||
URL string
|
||||
EndpointType int
|
||||
EndpointCreationType endpointCreationEnum
|
||||
PublicURL string
|
||||
GroupID int
|
||||
TLS bool
|
||||
|
@ -36,6 +39,17 @@ type endpointCreatePayload struct {
|
|||
EdgeCheckinInterval int
|
||||
}
|
||||
|
||||
type endpointCreationEnum int
|
||||
|
||||
const (
|
||||
_ endpointCreationEnum = iota
|
||||
localDockerEnvironment
|
||||
agentEnvironment
|
||||
azureEnvironment
|
||||
edgeAgentEnvironment
|
||||
localKubernetesEnvironment
|
||||
)
|
||||
|
||||
func (payload *endpointCreatePayload) Validate(r *http.Request) error {
|
||||
name, err := request.RetrieveMultiPartFormValue(r, "Name", false)
|
||||
if err != nil {
|
||||
|
@ -43,11 +57,11 @@ func (payload *endpointCreatePayload) Validate(r *http.Request) error {
|
|||
}
|
||||
payload.Name = name
|
||||
|
||||
endpointType, err := request.RetrieveNumericMultiPartFormValue(r, "EndpointType", false)
|
||||
if err != nil || endpointType == 0 {
|
||||
return errors.New("Invalid endpoint type value. Value must be one of: 1 (Docker environment), 2 (Agent environment), 3 (Azure environment) or 4 (Edge Agent environment)")
|
||||
endpointCreationType, err := request.RetrieveNumericMultiPartFormValue(r, "EndpointCreationType", false)
|
||||
if err != nil || endpointCreationType == 0 {
|
||||
return errors.New("Invalid endpoint type value. Value must be one of: 1 (Docker environment), 2 (Agent environment), 3 (Azure environment), 4 (Edge Agent environment) or 5 (Local Kubernetes environment)")
|
||||
}
|
||||
payload.EndpointType = endpointType
|
||||
payload.EndpointCreationType = endpointCreationEnum(endpointCreationType)
|
||||
|
||||
groupID, _ := request.RetrieveNumericMultiPartFormValue(r, "GroupID", true)
|
||||
if groupID == 0 {
|
||||
|
@ -97,8 +111,8 @@ func (payload *endpointCreatePayload) Validate(r *http.Request) error {
|
|||
}
|
||||
}
|
||||
|
||||
switch portainer.EndpointType(payload.EndpointType) {
|
||||
case portainer.AzureEnvironment:
|
||||
switch payload.EndpointCreationType {
|
||||
case azureEnvironment:
|
||||
azureApplicationID, err := request.RetrieveMultiPartFormValue(r, "AzureApplicationID", false)
|
||||
if err != nil {
|
||||
return errors.New("Invalid Azure application ID")
|
||||
|
@ -182,22 +196,34 @@ func (handler *Handler) endpointCreate(w http.ResponseWriter, r *http.Request) *
|
|||
}
|
||||
|
||||
func (handler *Handler) createEndpoint(payload *endpointCreatePayload) (*portainer.Endpoint, *httperror.HandlerError) {
|
||||
switch portainer.EndpointType(payload.EndpointType) {
|
||||
case portainer.AzureEnvironment:
|
||||
switch payload.EndpointCreationType {
|
||||
case azureEnvironment:
|
||||
return handler.createAzureEndpoint(payload)
|
||||
|
||||
case portainer.EdgeAgentOnDockerEnvironment:
|
||||
return handler.createEdgeAgentEndpoint(payload, portainer.EdgeAgentOnDockerEnvironment)
|
||||
case edgeAgentEnvironment:
|
||||
return handler.createEdgeAgentEndpoint(payload)
|
||||
|
||||
case portainer.KubernetesLocalEnvironment:
|
||||
case localKubernetesEnvironment:
|
||||
return handler.createKubernetesEndpoint(payload)
|
||||
}
|
||||
|
||||
case portainer.EdgeAgentOnKubernetesEnvironment:
|
||||
return handler.createEdgeAgentEndpoint(payload, portainer.EdgeAgentOnKubernetesEnvironment)
|
||||
endpointType := portainer.DockerEnvironment
|
||||
if payload.EndpointCreationType == agentEnvironment {
|
||||
agentPlatform, err := handler.pingAndCheckPlatform(payload)
|
||||
if err != nil {
|
||||
return nil, &httperror.HandlerError{http.StatusInternalServerError, "Unable to get endpoint type", err}
|
||||
}
|
||||
|
||||
if agentPlatform == portainer.AgentPlatformDocker {
|
||||
endpointType = portainer.AgentOnDockerEnvironment
|
||||
} else if agentPlatform == portainer.AgentPlatformKubernetes {
|
||||
endpointType = portainer.AgentOnKubernetesEnvironment
|
||||
payload.URL = strings.TrimPrefix(payload.URL, "tcp://")
|
||||
}
|
||||
}
|
||||
|
||||
if payload.TLS {
|
||||
return handler.createTLSSecuredEndpoint(payload, portainer.EndpointType(payload.EndpointType))
|
||||
return handler.createTLSSecuredEndpoint(payload, endpointType)
|
||||
}
|
||||
return handler.createUnsecuredEndpoint(payload)
|
||||
}
|
||||
|
@ -241,7 +267,7 @@ func (handler *Handler) createAzureEndpoint(payload *endpointCreatePayload) (*po
|
|||
return endpoint, nil
|
||||
}
|
||||
|
||||
func (handler *Handler) createEdgeAgentEndpoint(payload *endpointCreatePayload, endpointType portainer.EndpointType) (*portainer.Endpoint, *httperror.HandlerError) {
|
||||
func (handler *Handler) createEdgeAgentEndpoint(payload *endpointCreatePayload) (*portainer.Endpoint, *httperror.HandlerError) {
|
||||
endpointID := handler.DataStore.Endpoint().GetNextIdentifier()
|
||||
|
||||
portainerURL, err := url.Parse(payload.URL)
|
||||
|
@ -264,7 +290,7 @@ func (handler *Handler) createEdgeAgentEndpoint(payload *endpointCreatePayload,
|
|||
ID: portainer.EndpointID(endpointID),
|
||||
Name: payload.Name,
|
||||
URL: portainerHost,
|
||||
Type: endpointType,
|
||||
Type: portainer.EdgeAgentOnDockerEnvironment,
|
||||
GroupID: portainer.EndpointGroupID(payload.GroupID),
|
||||
TLSConfig: portainer.TLSConfiguration{
|
||||
TLS: false,
|
||||
|
@ -472,3 +498,58 @@ func (handler *Handler) storeTLSFiles(endpoint *portainer.Endpoint, payload *end
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (handler *Handler) pingAndCheckPlatform(payload *endpointCreatePayload) (portainer.AgentPlatform, error) {
|
||||
httpCli := &http.Client{
|
||||
Timeout: 3 * time.Second,
|
||||
}
|
||||
|
||||
if payload.TLS {
|
||||
tlsConfig, err := crypto.CreateTLSConfigurationFromBytes(payload.TLSCACertFile, payload.TLSCertFile, payload.TLSKeyFile, payload.TLSSkipVerify, payload.TLSSkipClientVerify)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
httpCli.Transport = &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
}
|
||||
}
|
||||
|
||||
url, err := url.Parse(fmt.Sprintf("%s/ping", payload.URL))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
url.Scheme = "https"
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, url.String(), nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
resp, err := httpCli.Do(req)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return 0, fmt.Errorf("Failed request with status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
agentPlatformHeader := resp.Header.Get(portainer.HTTPResponseAgentPlatform)
|
||||
if agentPlatformHeader == "" {
|
||||
return 0, errors.New("Agent Platform Header is missing")
|
||||
}
|
||||
|
||||
agentPlatformNumber, err := strconv.Atoi(agentPlatformHeader)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if agentPlatformNumber == 0 {
|
||||
return 0, errors.New("Agent platform is invalid")
|
||||
}
|
||||
|
||||
return portainer.AgentPlatform(agentPlatformNumber), nil
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue