mirror of https://github.com/portainer/portainer
feat(kube): use local kubectl for all deployments (#5488)
parent
5ad3cacefd
commit
d4f581a596
|
@ -56,6 +56,32 @@ func (service *Service) GetTunnelDetails(endpointID portainer.EndpointID) *porta
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetActiveTunnel retrieves an active tunnel which allows communicating with edge agent
|
||||||
|
func (service *Service) GetActiveTunnel(endpoint *portainer.Endpoint) (*portainer.TunnelDetails, error) {
|
||||||
|
tunnel := service.GetTunnelDetails(endpoint.ID)
|
||||||
|
if tunnel.Status == portainer.EdgeAgentIdle || tunnel.Status == portainer.EdgeAgentManagementRequired {
|
||||||
|
err := service.SetTunnelStatusToRequired(endpoint.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed opening tunnel to endpoint: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if endpoint.EdgeCheckinInterval == 0 {
|
||||||
|
settings, err := service.dataStore.Settings().Settings()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed fetching settings from db: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint.EdgeCheckinInterval = settings.EdgeAgentCheckinInterval
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForAgentToConnect := time.Duration(endpoint.EdgeCheckinInterval) * time.Second
|
||||||
|
time.Sleep(waitForAgentToConnect * 2)
|
||||||
|
}
|
||||||
|
tunnel = service.GetTunnelDetails(endpoint.ID)
|
||||||
|
|
||||||
|
return tunnel, nil
|
||||||
|
}
|
||||||
|
|
||||||
// SetTunnelStatusToActive update the status of the tunnel associated to the specified environment(endpoint).
|
// SetTunnelStatusToActive update the status of the tunnel associated to the specified environment(endpoint).
|
||||||
// It sets the status to ACTIVE.
|
// It sets the status to ACTIVE.
|
||||||
func (service *Service) SetTunnelStatusToActive(endpointID portainer.EndpointID) {
|
func (service *Service) SetTunnelStatusToActive(endpointID portainer.EndpointID) {
|
||||||
|
|
|
@ -99,8 +99,8 @@ func initSwarmStackManager(assetsPath string, configPath string, signatureServic
|
||||||
return exec.NewSwarmStackManager(assetsPath, configPath, signatureService, fileService, reverseTunnelService)
|
return exec.NewSwarmStackManager(assetsPath, configPath, signatureService, fileService, reverseTunnelService)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initKubernetesDeployer(kubernetesTokenCacheManager *kubeproxy.TokenCacheManager, kubernetesClientFactory *kubecli.ClientFactory, dataStore portainer.DataStore, reverseTunnelService portainer.ReverseTunnelService, signatureService portainer.DigitalSignatureService, assetsPath string) portainer.KubernetesDeployer {
|
func initKubernetesDeployer(kubernetesTokenCacheManager *kubeproxy.TokenCacheManager, kubernetesClientFactory *kubecli.ClientFactory, dataStore portainer.DataStore, reverseTunnelService portainer.ReverseTunnelService, signatureService portainer.DigitalSignatureService, proxyManager *proxy.Manager, assetsPath string) portainer.KubernetesDeployer {
|
||||||
return exec.NewKubernetesDeployer(kubernetesTokenCacheManager, kubernetesClientFactory, dataStore, reverseTunnelService, signatureService, assetsPath)
|
return exec.NewKubernetesDeployer(kubernetesTokenCacheManager, kubernetesClientFactory, dataStore, reverseTunnelService, signatureService, proxyManager, assetsPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initHelmPackageManager(assetsPath string) (libhelm.HelmPackageManager, error) {
|
func initHelmPackageManager(assetsPath string) (libhelm.HelmPackageManager, error) {
|
||||||
|
@ -469,7 +469,7 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||||
log.Fatalf("failed initializing swarm stack manager: %s", err)
|
log.Fatalf("failed initializing swarm stack manager: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
kubernetesDeployer := initKubernetesDeployer(kubernetesTokenCacheManager, kubernetesClientFactory, dataStore, reverseTunnelService, digitalSignatureService, *flags.Assets)
|
kubernetesDeployer := initKubernetesDeployer(kubernetesTokenCacheManager, kubernetesClientFactory, dataStore, reverseTunnelService, digitalSignatureService, proxyManager, *flags.Assets)
|
||||||
|
|
||||||
helmPackageManager, err := initHelmPackageManager(*flags.Assets)
|
helmPackageManager, err := initHelmPackageManager(*flags.Assets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -47,7 +47,7 @@ func (manager *ComposeStackManager) ComposeSyntaxMaxVersion() string {
|
||||||
func (manager *ComposeStackManager) Up(ctx context.Context, stack *portainer.Stack, endpoint *portainer.Endpoint) error {
|
func (manager *ComposeStackManager) Up(ctx context.Context, stack *portainer.Stack, endpoint *portainer.Endpoint) error {
|
||||||
url, proxy, err := manager.fetchEndpointProxy(endpoint)
|
url, proxy, err := manager.fetchEndpointProxy(endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to featch environment proxy")
|
return errors.Wrap(err, "failed to fetch endpoint proxy")
|
||||||
}
|
}
|
||||||
|
|
||||||
if proxy != nil {
|
if proxy != nil {
|
||||||
|
@ -90,12 +90,12 @@ func (manager *ComposeStackManager) fetchEndpointProxy(endpoint *portainer.Endpo
|
||||||
return "", nil, nil
|
return "", nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
proxy, err := manager.proxyManager.CreateComposeProxyServer(endpoint)
|
proxy, err := manager.proxyManager.CreateAgentProxyServer(endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("tcp://127.0.0.1:%d", proxy.Port), proxy, nil
|
return fmt.Sprintf("http://127.0.0.1:%d", proxy.Port), proxy, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createEnvFile(stack *portainer.Stack) (string, error) {
|
func createEnvFile(stack *portainer.Stack) (string, error) {
|
||||||
|
|
|
@ -2,24 +2,21 @@ package exec
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/portainer/portainer/api/http/proxy"
|
||||||
|
"github.com/portainer/portainer/api/http/proxy/factory"
|
||||||
"github.com/portainer/portainer/api/http/proxy/factory/kubernetes"
|
"github.com/portainer/portainer/api/http/proxy/factory/kubernetes"
|
||||||
"github.com/portainer/portainer/api/http/security"
|
"github.com/portainer/portainer/api/http/security"
|
||||||
"github.com/portainer/portainer/api/kubernetes/cli"
|
"github.com/portainer/portainer/api/kubernetes/cli"
|
||||||
|
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/crypto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// KubernetesDeployer represents a service to deploy resources inside a Kubernetes environment(endpoint).
|
// KubernetesDeployer represents a service to deploy resources inside a Kubernetes environment(endpoint).
|
||||||
|
@ -30,10 +27,11 @@ type KubernetesDeployer struct {
|
||||||
signatureService portainer.DigitalSignatureService
|
signatureService portainer.DigitalSignatureService
|
||||||
kubernetesClientFactory *cli.ClientFactory
|
kubernetesClientFactory *cli.ClientFactory
|
||||||
kubernetesTokenCacheManager *kubernetes.TokenCacheManager
|
kubernetesTokenCacheManager *kubernetes.TokenCacheManager
|
||||||
|
proxyManager *proxy.Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewKubernetesDeployer initializes a new KubernetesDeployer service.
|
// NewKubernetesDeployer initializes a new KubernetesDeployer service.
|
||||||
func NewKubernetesDeployer(kubernetesTokenCacheManager *kubernetes.TokenCacheManager, kubernetesClientFactory *cli.ClientFactory, datastore portainer.DataStore, reverseTunnelService portainer.ReverseTunnelService, signatureService portainer.DigitalSignatureService, binaryPath string) *KubernetesDeployer {
|
func NewKubernetesDeployer(kubernetesTokenCacheManager *kubernetes.TokenCacheManager, kubernetesClientFactory *cli.ClientFactory, datastore portainer.DataStore, reverseTunnelService portainer.ReverseTunnelService, signatureService portainer.DigitalSignatureService, proxyManager *proxy.Manager, binaryPath string) *KubernetesDeployer {
|
||||||
return &KubernetesDeployer{
|
return &KubernetesDeployer{
|
||||||
binaryPath: binaryPath,
|
binaryPath: binaryPath,
|
||||||
dataStore: datastore,
|
dataStore: datastore,
|
||||||
|
@ -41,6 +39,7 @@ func NewKubernetesDeployer(kubernetesTokenCacheManager *kubernetes.TokenCacheMan
|
||||||
signatureService: signatureService,
|
signatureService: signatureService,
|
||||||
kubernetesClientFactory: kubernetesClientFactory,
|
kubernetesClientFactory: kubernetesClientFactory,
|
||||||
kubernetesTokenCacheManager: kubernetesTokenCacheManager,
|
kubernetesTokenCacheManager: kubernetesTokenCacheManager,
|
||||||
|
proxyManager: proxyManager,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,14 +49,14 @@ func (deployer *KubernetesDeployer) getToken(request *http.Request, endpoint *po
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
kubecli, err := deployer.kubernetesClientFactory.GetKubeClient(endpoint)
|
kubeCLI, err := deployer.kubernetesClientFactory.GetKubeClient(endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenCache := deployer.kubernetesTokenCacheManager.GetOrCreateTokenCache(int(endpoint.ID))
|
tokenCache := deployer.kubernetesTokenCacheManager.GetOrCreateTokenCache(int(endpoint.ID))
|
||||||
|
|
||||||
tokenManager, err := kubernetes.NewTokenManager(kubecli, deployer.dataStore, tokenCache, setLocalAdminToken)
|
tokenManager, err := kubernetes.NewTokenManager(kubeCLI, deployer.dataStore, tokenCache, setLocalAdminToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -80,153 +79,44 @@ func (deployer *KubernetesDeployer) getToken(request *http.Request, endpoint *po
|
||||||
// Deploy will deploy a Kubernetes manifest inside a specific namespace in a Kubernetes environment(endpoint).
|
// Deploy will deploy a Kubernetes manifest inside a specific namespace in a Kubernetes environment(endpoint).
|
||||||
// Otherwise it will use kubectl to deploy the manifest.
|
// Otherwise it will use kubectl to deploy the manifest.
|
||||||
func (deployer *KubernetesDeployer) Deploy(request *http.Request, endpoint *portainer.Endpoint, stackConfig string, namespace string) (string, error) {
|
func (deployer *KubernetesDeployer) Deploy(request *http.Request, endpoint *portainer.Endpoint, stackConfig string, namespace string) (string, error) {
|
||||||
if endpoint.Type == portainer.KubernetesLocalEnvironment {
|
command := path.Join(deployer.binaryPath, "kubectl")
|
||||||
token, err := deployer.getToken(request, endpoint, true)
|
if runtime.GOOS == "windows" {
|
||||||
|
command = path.Join(deployer.binaryPath, "kubectl.exe")
|
||||||
|
}
|
||||||
|
|
||||||
|
args := make([]string, 0)
|
||||||
|
|
||||||
|
if endpoint.Type == portainer.AgentOnKubernetesEnvironment || endpoint.Type == portainer.EdgeAgentOnKubernetesEnvironment {
|
||||||
|
url, proxy, err := deployer.getAgentURL(endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", errors.WithMessage(err, "failed generating endpoint URL")
|
||||||
}
|
}
|
||||||
|
|
||||||
command := path.Join(deployer.binaryPath, "kubectl")
|
defer proxy.Close()
|
||||||
if runtime.GOOS == "windows" {
|
args = append(args, "--server", url)
|
||||||
command = path.Join(deployer.binaryPath, "kubectl.exe")
|
|
||||||
}
|
|
||||||
|
|
||||||
args := make([]string, 0)
|
|
||||||
args = append(args, "--server", endpoint.URL)
|
|
||||||
args = append(args, "--insecure-skip-tls-verify")
|
args = append(args, "--insecure-skip-tls-verify")
|
||||||
args = append(args, "--token", token)
|
|
||||||
args = append(args, "--namespace", namespace)
|
|
||||||
args = append(args, "apply", "-f", "-")
|
|
||||||
|
|
||||||
var stderr bytes.Buffer
|
|
||||||
cmd := exec.Command(command, args...)
|
|
||||||
cmd.Stderr = &stderr
|
|
||||||
cmd.Stdin = strings.NewReader(stackConfig)
|
|
||||||
|
|
||||||
output, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
return "", errors.New(stderr.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(output), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// agent
|
token, err := deployer.getToken(request, endpoint, endpoint.Type == portainer.KubernetesLocalEnvironment)
|
||||||
|
|
||||||
endpointURL := endpoint.URL
|
|
||||||
if endpoint.Type == portainer.EdgeAgentOnKubernetesEnvironment {
|
|
||||||
tunnel := deployer.reverseTunnelService.GetTunnelDetails(endpoint.ID)
|
|
||||||
if tunnel.Status == portainer.EdgeAgentIdle {
|
|
||||||
|
|
||||||
err := deployer.reverseTunnelService.SetTunnelStatusToRequired(endpoint.ID)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
settings, err := deployer.dataStore.Settings().Settings()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
waitForAgentToConnect := time.Duration(settings.EdgeAgentCheckinInterval) * time.Second
|
|
||||||
time.Sleep(waitForAgentToConnect * 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
endpointURL = fmt.Sprintf("http://127.0.0.1:%d", tunnel.Port)
|
|
||||||
}
|
|
||||||
|
|
||||||
transport := &http.Transport{}
|
|
||||||
|
|
||||||
if endpoint.TLSConfig.TLS {
|
|
||||||
tlsConfig, err := crypto.CreateTLSConfigurationFromDisk(endpoint.TLSConfig.TLSCACertPath, endpoint.TLSConfig.TLSCertPath, endpoint.TLSConfig.TLSKeyPath, endpoint.TLSConfig.TLSSkipVerify)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
transport.TLSClientConfig = tlsConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
httpCli := &http.Client{
|
|
||||||
Transport: transport,
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.HasPrefix(endpointURL, "http") {
|
|
||||||
endpointURL = fmt.Sprintf("https://%s", endpointURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
url, err := url.Parse(fmt.Sprintf("%s/v2/kubernetes/stack", endpointURL))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
reqPayload, err := json.Marshal(
|
args = append(args, "--token", token)
|
||||||
struct {
|
args = append(args, "--namespace", namespace)
|
||||||
StackConfig string
|
args = append(args, "apply", "-f", "-")
|
||||||
Namespace string
|
|
||||||
}{
|
var stderr bytes.Buffer
|
||||||
StackConfig: stackConfig,
|
cmd := exec.Command(command, args...)
|
||||||
Namespace: namespace,
|
cmd.Stderr = &stderr
|
||||||
})
|
cmd.Stdin = strings.NewReader(stackConfig)
|
||||||
|
|
||||||
|
output, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", errors.Wrapf(err, "failed to execute kubectl command: %q", stderr.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodPost, url.String(), bytes.NewReader(reqPayload))
|
return string(output), nil
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
signature, err := deployer.signatureService.CreateSignature(portainer.PortainerAgentSignatureMessage)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
token, err := deployer.getToken(request, endpoint, false)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Set(portainer.PortainerAgentPublicKeyHeader, deployer.signatureService.EncodedPublicKey())
|
|
||||||
req.Header.Set(portainer.PortainerAgentSignatureHeader, signature)
|
|
||||||
req.Header.Set(portainer.PortainerAgentKubernetesSATokenHeader, token)
|
|
||||||
|
|
||||||
resp, err := httpCli.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
var errorResponseData struct {
|
|
||||||
Message string
|
|
||||||
Details string
|
|
||||||
}
|
|
||||||
err = json.NewDecoder(resp.Body).Decode(&errorResponseData)
|
|
||||||
if err != nil {
|
|
||||||
output, parseStringErr := ioutil.ReadAll(resp.Body)
|
|
||||||
if parseStringErr != nil {
|
|
||||||
return "", parseStringErr
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", fmt.Errorf("Failed parsing, body: %s, error: %w", output, err)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", fmt.Errorf("Deployment to agent failed: %s", errorResponseData.Details)
|
|
||||||
}
|
|
||||||
|
|
||||||
var responseData struct{ Output string }
|
|
||||||
err = json.NewDecoder(resp.Body).Decode(&responseData)
|
|
||||||
if err != nil {
|
|
||||||
parsedOutput, parseStringErr := ioutil.ReadAll(resp.Body)
|
|
||||||
if parseStringErr != nil {
|
|
||||||
return "", parseStringErr
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", fmt.Errorf("Failed decoding, body: %s, err: %w", parsedOutput, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return responseData.Output, nil
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConvertCompose leverages the kompose binary to deploy a compose compliant manifest.
|
// ConvertCompose leverages the kompose binary to deploy a compose compliant manifest.
|
||||||
|
@ -251,3 +141,12 @@ func (deployer *KubernetesDeployer) ConvertCompose(data []byte) ([]byte, error)
|
||||||
|
|
||||||
return output, nil
|
return output, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (deployer *KubernetesDeployer) getAgentURL(endpoint *portainer.Endpoint) (string, *factory.ProxyServer, error) {
|
||||||
|
proxy, err := deployer.proxyManager.CreateAgentProxyServer(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("http://127.0.0.1:%d/kubernetes", proxy.Port), proxy, nil
|
||||||
|
}
|
||||||
|
|
|
@ -6,29 +6,36 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/crypto"
|
"github.com/portainer/portainer/api/crypto"
|
||||||
"github.com/portainer/portainer/api/http/proxy/factory/dockercompose"
|
"github.com/portainer/portainer/api/http/proxy/factory/agent"
|
||||||
|
"github.com/portainer/portainer/api/internal/endpointutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProxyServer provide an extedned proxy with a local server to forward requests
|
// ProxyServer provide an extended proxy with a local server to forward requests
|
||||||
type ProxyServer struct {
|
type ProxyServer struct {
|
||||||
server *http.Server
|
server *http.Server
|
||||||
Port int
|
Port int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (factory *ProxyFactory) NewDockerComposeAgentProxy(endpoint *portainer.Endpoint) (*ProxyServer, error) {
|
// NewAgentProxy creates a new instance of ProxyServer that wrap http requests with agent headers
|
||||||
|
func (factory *ProxyFactory) NewAgentProxy(endpoint *portainer.Endpoint) (*ProxyServer, error) {
|
||||||
|
urlString := endpoint.URL
|
||||||
|
if endpointutils.IsEdgeEndpoint((endpoint)) {
|
||||||
|
tunnel, err := factory.reverseTunnelService.GetActiveTunnel(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed starting tunnel")
|
||||||
|
}
|
||||||
|
|
||||||
if endpoint.Type == portainer.EdgeAgentOnDockerEnvironment {
|
urlString = fmt.Sprintf("http://127.0.0.1:%d", tunnel.Port)
|
||||||
return &ProxyServer{
|
|
||||||
Port: factory.reverseTunnelService.GetTunnelDetails(endpoint.ID).Port,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
endpointURL, err := url.Parse(endpoint.URL)
|
endpointURL, err := parseURL(urlString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, errors.Wrapf(err, "failed parsing url %s", endpoint.URL)
|
||||||
}
|
}
|
||||||
|
|
||||||
endpointURL.Scheme = "http"
|
endpointURL.Scheme = "http"
|
||||||
|
@ -37,7 +44,7 @@ func (factory *ProxyFactory) NewDockerComposeAgentProxy(endpoint *portainer.Endp
|
||||||
if endpoint.TLSConfig.TLS || endpoint.TLSConfig.TLSSkipVerify {
|
if endpoint.TLSConfig.TLS || endpoint.TLSConfig.TLSSkipVerify {
|
||||||
config, err := crypto.CreateTLSConfigurationFromDisk(endpoint.TLSConfig.TLSCACertPath, endpoint.TLSConfig.TLSCertPath, endpoint.TLSConfig.TLSKeyPath, endpoint.TLSConfig.TLSSkipVerify)
|
config, err := crypto.CreateTLSConfigurationFromDisk(endpoint.TLSConfig.TLSCACertPath, endpoint.TLSConfig.TLSCertPath, endpoint.TLSConfig.TLSKeyPath, endpoint.TLSConfig.TLSSkipVerify)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, errors.WithMessage(err, "failed generating tls configuration")
|
||||||
}
|
}
|
||||||
|
|
||||||
httpTransport.TLSClientConfig = config
|
httpTransport.TLSClientConfig = config
|
||||||
|
@ -46,7 +53,7 @@ func (factory *ProxyFactory) NewDockerComposeAgentProxy(endpoint *portainer.Endp
|
||||||
|
|
||||||
proxy := newSingleHostReverseProxyWithHostHeader(endpointURL)
|
proxy := newSingleHostReverseProxyWithHostHeader(endpointURL)
|
||||||
|
|
||||||
proxy.Transport = dockercompose.NewAgentTransport(factory.signatureService, httpTransport)
|
proxy.Transport = agent.NewTransport(factory.signatureService, httpTransport)
|
||||||
|
|
||||||
proxyServer := &ProxyServer{
|
proxyServer := &ProxyServer{
|
||||||
server: &http.Server{
|
server: &http.Server{
|
||||||
|
@ -57,7 +64,7 @@ func (factory *ProxyFactory) NewDockerComposeAgentProxy(endpoint *portainer.Endp
|
||||||
|
|
||||||
err = proxyServer.start()
|
err = proxyServer.start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, errors.Wrap(err, "failed starting proxy server")
|
||||||
}
|
}
|
||||||
|
|
||||||
return proxyServer, nil
|
return proxyServer, nil
|
||||||
|
@ -91,3 +98,15 @@ func (proxy *ProxyServer) Close() {
|
||||||
proxy.server.Close()
|
proxy.server.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseURL parses the endpointURL using url.Parse.
|
||||||
|
//
|
||||||
|
// to prevent an error when url has port but no protocol prefix
|
||||||
|
// we add `//` prefix if needed
|
||||||
|
func parseURL(endpointURL string) (*url.URL, error) {
|
||||||
|
if !strings.HasPrefix(endpointURL, "http") && !strings.HasPrefix(endpointURL, "tcp") && !strings.HasPrefix(endpointURL, "//") {
|
||||||
|
endpointURL = fmt.Sprintf("//%s", endpointURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.Parse(endpointURL)
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package dockercompose
|
package agent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -7,17 +7,17 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// AgentTransport is an http.Transport wrapper that adds custom http headers to communicate to an Agent
|
// Transport is an http.Transport wrapper that adds custom http headers to communicate to an Agent
|
||||||
AgentTransport struct {
|
Transport struct {
|
||||||
httpTransport *http.Transport
|
httpTransport *http.Transport
|
||||||
signatureService portainer.DigitalSignatureService
|
signatureService portainer.DigitalSignatureService
|
||||||
endpointIdentifier portainer.EndpointID
|
endpointIdentifier portainer.EndpointID
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewAgentTransport returns a new transport that can be used to send signed requests to a Portainer agent
|
// NewTransport returns a new transport that can be used to send signed requests to a Portainer agent
|
||||||
func NewAgentTransport(signatureService portainer.DigitalSignatureService, httpTransport *http.Transport) *AgentTransport {
|
func NewTransport(signatureService portainer.DigitalSignatureService, httpTransport *http.Transport) *Transport {
|
||||||
transport := &AgentTransport{
|
transport := &Transport{
|
||||||
httpTransport: httpTransport,
|
httpTransport: httpTransport,
|
||||||
signatureService: signatureService,
|
signatureService: signatureService,
|
||||||
}
|
}
|
||||||
|
@ -26,8 +26,7 @@ func NewAgentTransport(signatureService portainer.DigitalSignatureService, httpT
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoundTrip is the implementation of the the http.RoundTripper interface
|
// RoundTrip is the implementation of the the http.RoundTripper interface
|
||||||
func (transport *AgentTransport) RoundTrip(request *http.Request) (*http.Response, error) {
|
func (transport *Transport) RoundTrip(request *http.Request) (*http.Response, error) {
|
||||||
|
|
||||||
signature, err := transport.signatureService.CreateSignature(portainer.PortainerAgentSignatureMessage)
|
signature, err := transport.signatureService.CreateSignature(portainer.PortainerAgentSignatureMessage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
|
@ -48,10 +48,10 @@ func (manager *Manager) CreateAndRegisterEndpointProxy(endpoint *portainer.Endpo
|
||||||
return proxy, nil
|
return proxy, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateComposeProxyServer creates a new HTTP reverse proxy based on environment(endpoint) properties and and adds it to the registered proxies.
|
// CreateAgentProxyServer creates a new HTTP reverse proxy based on environment(endpoint) properties and and adds it to the registered proxies.
|
||||||
// It can also be used to create a new HTTP reverse proxy and replace an already registered proxy.
|
// It can also be used to create a new HTTP reverse proxy and replace an already registered proxy.
|
||||||
func (manager *Manager) CreateComposeProxyServer(endpoint *portainer.Endpoint) (*factory.ProxyServer, error) {
|
func (manager *Manager) CreateAgentProxyServer(endpoint *portainer.Endpoint) (*factory.ProxyServer, error) {
|
||||||
return manager.proxyFactory.NewDockerComposeAgentProxy(endpoint)
|
return manager.proxyFactory.NewAgentProxy(endpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetEndpointProxy returns the proxy associated to a key
|
// GetEndpointProxy returns the proxy associated to a key
|
||||||
|
|
|
@ -24,3 +24,7 @@ func IsDockerEndpoint(endpoint *portainer.Endpoint) bool {
|
||||||
endpoint.Type == portainer.AgentOnDockerEnvironment ||
|
endpoint.Type == portainer.AgentOnDockerEnvironment ||
|
||||||
endpoint.Type == portainer.EdgeAgentOnDockerEnvironment
|
endpoint.Type == portainer.EdgeAgentOnDockerEnvironment
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsEdgeEndpoint(endpoint *portainer.Endpoint) bool {
|
||||||
|
return endpoint.Type == portainer.EdgeAgentOnDockerEnvironment || endpoint.Type == portainer.EdgeAgentOnKubernetesEnvironment
|
||||||
|
}
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
cmap "github.com/orcaman/concurrent-map"
|
cmap "github.com/orcaman/concurrent-map"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
|
@ -116,36 +115,18 @@ func (rt *agentHeaderRoundTripper) RoundTrip(req *http.Request) (*http.Response,
|
||||||
func (factory *ClientFactory) buildAgentClient(endpoint *portainer.Endpoint) (*kubernetes.Clientset, error) {
|
func (factory *ClientFactory) buildAgentClient(endpoint *portainer.Endpoint) (*kubernetes.Clientset, error) {
|
||||||
endpointURL := fmt.Sprintf("https://%s/kubernetes", endpoint.URL)
|
endpointURL := fmt.Sprintf("https://%s/kubernetes", endpoint.URL)
|
||||||
|
|
||||||
return factory.createRemoteClient(endpointURL);
|
return factory.createRemoteClient(endpointURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (factory *ClientFactory) buildEdgeClient(endpoint *portainer.Endpoint) (*kubernetes.Clientset, error) {
|
func (factory *ClientFactory) buildEdgeClient(endpoint *portainer.Endpoint) (*kubernetes.Clientset, error) {
|
||||||
tunnel := factory.reverseTunnelService.GetTunnelDetails(endpoint.ID)
|
tunnel, err := factory.reverseTunnelService.GetActiveTunnel(endpoint)
|
||||||
|
if err != nil {
|
||||||
if tunnel.Status == portainer.EdgeAgentIdle {
|
return nil, errors.Wrap(err, "failed activating tunnel")
|
||||||
err := factory.reverseTunnelService.SetTunnelStatusToRequired(endpoint.ID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed opening tunnel to environment: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if endpoint.EdgeCheckinInterval == 0 {
|
|
||||||
settings, err := factory.dataStore.Settings().Settings()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed fetching settings from db: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
endpoint.EdgeCheckinInterval = settings.EdgeAgentCheckinInterval
|
|
||||||
}
|
|
||||||
|
|
||||||
waitForAgentToConnect := time.Duration(endpoint.EdgeCheckinInterval) * time.Second
|
|
||||||
time.Sleep(waitForAgentToConnect * 2)
|
|
||||||
|
|
||||||
tunnel = factory.reverseTunnelService.GetTunnelDetails(endpoint.ID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
endpointURL := fmt.Sprintf("http://127.0.0.1:%d/kubernetes", tunnel.Port)
|
endpointURL := fmt.Sprintf("http://127.0.0.1:%d/kubernetes", tunnel.Port)
|
||||||
|
|
||||||
return factory.createRemoteClient(endpointURL);
|
return factory.createRemoteClient(endpointURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (factory *ClientFactory) createRemoteClient(endpointURL string) (*kubernetes.Clientset, error) {
|
func (factory *ClientFactory) createRemoteClient(endpointURL string) (*kubernetes.Clientset, error) {
|
||||||
|
|
|
@ -1326,6 +1326,7 @@ type (
|
||||||
SetTunnelStatusToIdle(endpointID EndpointID)
|
SetTunnelStatusToIdle(endpointID EndpointID)
|
||||||
KeepTunnelAlive(endpointID EndpointID, ctx context.Context, maxKeepAlive time.Duration)
|
KeepTunnelAlive(endpointID EndpointID, ctx context.Context, maxKeepAlive time.Duration)
|
||||||
GetTunnelDetails(endpointID EndpointID) *TunnelDetails
|
GetTunnelDetails(endpointID EndpointID) *TunnelDetails
|
||||||
|
GetActiveTunnel(endpoint *Endpoint) (*TunnelDetails, error)
|
||||||
AddEdgeJob(endpointID EndpointID, edgeJob *EdgeJob)
|
AddEdgeJob(endpointID EndpointID, edgeJob *EdgeJob)
|
||||||
RemoveEdgeJob(edgeJobID EdgeJobID)
|
RemoveEdgeJob(edgeJobID EdgeJobID)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue