diff --git a/api/http/docker_handler.go b/api/http/docker_handler.go index cb89d0093..c50d3c23b 100644 --- a/api/http/docker_handler.go +++ b/api/http/docker_handler.go @@ -7,11 +7,9 @@ import ( "log" "net/http" - "net/url" "os" "github.com/gorilla/mux" - "github.com/orcaman/concurrent-map" ) // DockerHandler represents an HTTP API handler for proxying requests to the Docker API. @@ -19,8 +17,7 @@ type DockerHandler struct { *mux.Router Logger *log.Logger EndpointService portainer.EndpointService - ProxyFactory ProxyFactory - proxies cmap.ConcurrentMap + ProxyService *ProxyService } // NewDockerHandler returns a new instance of DockerHandler. @@ -28,10 +25,6 @@ func NewDockerHandler(mw *middleWareService, resourceControlService portainer.Re h := &DockerHandler{ Router: mux.NewRouter(), Logger: log.New(os.Stderr, "", log.LstdFlags), - ProxyFactory: ProxyFactory{ - ResourceControlService: resourceControlService, - }, - proxies: cmap.New(), } h.PathPrefix("/{id}/").Handler( mw.authenticated(http.HandlerFunc(h.proxyRequestsToDockerAPI))) @@ -74,41 +67,14 @@ func (handler *DockerHandler) proxyRequestsToDockerAPI(w http.ResponseWriter, r } var proxy http.Handler - item, ok := handler.proxies.Get(string(endpointID)) - if !ok { - proxy, err = handler.createAndRegisterEndpointProxy(endpoint) + proxy = handler.ProxyService.GetProxy(string(endpointID)) + if proxy == nil { + proxy, err = handler.ProxyService.CreateAndRegisterProxy(endpoint) if err != nil { Error(w, err, http.StatusBadRequest, handler.Logger) return } - } else { - proxy = item.(http.Handler) } + http.StripPrefix("/"+id, proxy).ServeHTTP(w, r) } - -func (handler *DockerHandler) createAndRegisterEndpointProxy(endpoint *portainer.Endpoint) (http.Handler, error) { - var proxy http.Handler - - endpointURL, err := url.Parse(endpoint.URL) - if err != nil { - return nil, err - } - - if endpointURL.Scheme == "tcp" { - if endpoint.TLS { - proxy, err = handler.ProxyFactory.newHTTPSProxy(endpointURL, endpoint) - if err != nil { - return nil, err - } - } else { - proxy = handler.ProxyFactory.newHTTPProxy(endpointURL) - } - } else { - // Assume unix:// scheme - proxy = handler.ProxyFactory.newSocketProxy(endpointURL.Path) - } - - handler.proxies.Set(string(endpoint.ID), proxy) - return proxy, nil -} diff --git a/api/http/endpoint_handler.go b/api/http/endpoint_handler.go index 2178ea475..162ceaa76 100644 --- a/api/http/endpoint_handler.go +++ b/api/http/endpoint_handler.go @@ -20,6 +20,7 @@ type EndpointHandler struct { authorizeEndpointManagement bool EndpointService portainer.EndpointService FileService portainer.FileService + ProxyService *ProxyService } const ( @@ -281,6 +282,12 @@ func (handler *EndpointHandler) handlePutEndpoint(w http.ResponseWriter, r *http } } + _, err = handler.ProxyService.CreateAndRegisterProxy(endpoint) + if err != nil { + Error(w, err, http.StatusInternalServerError, handler.Logger) + return + } + err = handler.EndpointService.UpdateEndpoint(endpoint.ID, endpoint) if err != nil { Error(w, err, http.StatusInternalServerError, handler.Logger) @@ -320,6 +327,8 @@ func (handler *EndpointHandler) handleDeleteEndpoint(w http.ResponseWriter, r *h return } + handler.ProxyService.DeleteProxy(string(endpointID)) + err = handler.EndpointService.DeleteEndpoint(portainer.EndpointID(endpointID)) if err != nil { Error(w, err, http.StatusInternalServerError, handler.Logger) diff --git a/api/http/proxy.go b/api/http/proxy.go new file mode 100644 index 000000000..053656be5 --- /dev/null +++ b/api/http/proxy.go @@ -0,0 +1,67 @@ +package http + +import ( + "net/http" + "net/url" + + "github.com/orcaman/concurrent-map" + "github.com/portainer/portainer" +) + +// ProxyService represents a service used to manage Docker proxies. +type ProxyService struct { + proxyFactory *ProxyFactory + proxies cmap.ConcurrentMap +} + +// NewProxyService initializes a new ProxyService +func NewProxyService(resourceControlService portainer.ResourceControlService) *ProxyService { + return &ProxyService{ + proxies: cmap.New(), + proxyFactory: &ProxyFactory{ + ResourceControlService: resourceControlService, + }, + } +} + +// CreateAndRegisterProxy creates a new HTTP reverse proxy 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. +func (service *ProxyService) CreateAndRegisterProxy(endpoint *portainer.Endpoint) (http.Handler, error) { + var proxy http.Handler + + endpointURL, err := url.Parse(endpoint.URL) + if err != nil { + return nil, err + } + + if endpointURL.Scheme == "tcp" { + if endpoint.TLS { + proxy, err = service.proxyFactory.newHTTPSProxy(endpointURL, endpoint) + if err != nil { + return nil, err + } + } else { + proxy = service.proxyFactory.newHTTPProxy(endpointURL) + } + } else { + // Assume unix:// scheme + proxy = service.proxyFactory.newSocketProxy(endpointURL.Path) + } + + service.proxies.Set(string(endpoint.ID), proxy) + return proxy, nil +} + +// GetProxy returns the proxy associated to a key +func (service *ProxyService) GetProxy(key string) http.Handler { + proxy, ok := service.proxies.Get(key) + if !ok { + return nil + } + return proxy.(http.Handler) +} + +// DeleteProxy deletes the proxy associated to a key +func (service *ProxyService) DeleteProxy(key string) { + service.proxies.Remove(key) +} diff --git a/api/http/server.go b/api/http/server.go index 974174b5c..916e2370f 100644 --- a/api/http/server.go +++ b/api/http/server.go @@ -29,6 +29,7 @@ func (server *Server) Start() error { jwtService: server.JWTService, authDisabled: server.AuthDisabled, } + proxyService := NewProxyService(server.ResourceControlService) var authHandler = NewAuthHandler(middleWareService) authHandler.UserService = server.UserService @@ -45,12 +46,14 @@ func (server *Server) Start() error { templatesHandler.containerTemplatesURL = server.TemplatesURL var dockerHandler = NewDockerHandler(middleWareService, server.ResourceControlService) dockerHandler.EndpointService = server.EndpointService + dockerHandler.ProxyService = proxyService var websocketHandler = NewWebSocketHandler() websocketHandler.EndpointService = server.EndpointService var endpointHandler = NewEndpointHandler(middleWareService) endpointHandler.authorizeEndpointManagement = server.EndpointManagement endpointHandler.EndpointService = server.EndpointService endpointHandler.FileService = server.FileService + endpointHandler.ProxyService = proxyService var uploadHandler = NewUploadHandler(middleWareService) uploadHandler.FileService = server.FileService var fileHandler = newFileHandler(server.AssetsPath) diff --git a/api/portainer.go b/api/portainer.go index b4772f771..fe02d6732 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -1,8 +1,6 @@ package portainer -import ( - "io" -) +import "io" type ( // Pair defines a key/value string pair diff --git a/app/components/endpoint/endpointController.js b/app/components/endpoint/endpointController.js index b221a4f14..c455e38dc 100644 --- a/app/components/endpoint/endpointController.js +++ b/app/components/endpoint/endpointController.js @@ -10,6 +10,7 @@ function ($scope, $state, $stateParams, $filter, EndpointService, Messages) { error: '', uploadInProgress: false }; + $scope.formValues = { TLSCACert: null, TLSCert: null, diff --git a/app/services/endpointService.js b/app/services/endpointService.js index 9202c695e..e9486cd29 100644 --- a/app/services/endpointService.js +++ b/app/services/endpointService.js @@ -24,13 +24,14 @@ angular.module('portainer.services') if (endpointParams.type && endpointParams.URL) { query.URL = endpointParams.type === 'local' ? ("unix://" + endpointParams.URL) : ("tcp://" + endpointParams.URL); } + var deferred = $q.defer(); - Endpoints.update({id: id}, query).$promise + FileUploadService.uploadTLSFilesForEndpoint(id, endpointParams.TLSCACert, endpointParams.TLSCert, endpointParams.TLSKey) .then(function success() { - return FileUploadService.uploadTLSFilesForEndpoint(id, endpointParams.TLSCAFile, endpointParams.TLSCertFile, endpointParams.TLSKeyFile); + deferred.notify({upload: false}); + return Endpoints.update({id: id}, query).$promise; }) .then(function success(data) { - deferred.notify({upload: false}); deferred.resolve(data); }) .catch(function error(err) {