1
0
Fork 0
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:
Chaim Lev-Ari 2020-08-04 03:44:17 +03:00 committed by GitHub
parent 490b7ad26f
commit bd7d7dcef5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 226 additions and 76 deletions

View file

@ -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
}