mirror of https://github.com/portainer/portainer
feat(storidge): introduce endpoint extensions and proxy Storidge API (#1661)
parent
b5e256c967
commit
eb43579378
|
@ -0,0 +1,20 @@
|
|||
package bolt
|
||||
|
||||
import "github.com/portainer/portainer"
|
||||
|
||||
func (m *Migrator) updateEndpointsToVersion8() error {
|
||||
legacyEndpoints, err := m.EndpointService.Endpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, endpoint := range legacyEndpoints {
|
||||
endpoint.Extensions = []portainer.EndpointExtension{}
|
||||
err = m.EndpointService.UpdateEndpoint(endpoint.ID, &endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -89,6 +89,13 @@ func (m *Migrator) Migrate() error {
|
|||
}
|
||||
}
|
||||
|
||||
if m.CurrentDBVersion < 8 {
|
||||
err := m.updateEndpointsToVersion8()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err := m.VersionService.StoreDBVersion(portainer.DBVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -218,6 +218,7 @@ func main() {
|
|||
},
|
||||
AuthorizedUsers: []portainer.UserID{},
|
||||
AuthorizedTeams: []portainer.TeamID{},
|
||||
Extensions: []portainer.EndpointExtension{},
|
||||
}
|
||||
err = store.EndpointService.CreateEndpoint(endpoint)
|
||||
if err != nil {
|
||||
|
|
|
@ -57,6 +57,12 @@ const (
|
|||
ErrComposeFileNotFoundInRepository = Error("Unable to find a Compose file in the repository")
|
||||
)
|
||||
|
||||
// Endpoint extensions error
|
||||
const (
|
||||
ErrEndpointExtensionNotSupported = Error("This extension is not supported")
|
||||
ErrEndpointExtensionAlreadyAssociated = Error("This extension is already associated to the endpoint")
|
||||
)
|
||||
|
||||
// Version errors.
|
||||
const (
|
||||
ErrDBVersionNotFound = Error("DB version not found")
|
||||
|
|
|
@ -35,24 +35,6 @@ func NewDockerHandler(bouncer *security.RequestBouncer) *DockerHandler {
|
|||
return h
|
||||
}
|
||||
|
||||
func (handler *DockerHandler) checkEndpointAccessControl(endpoint *portainer.Endpoint, userID portainer.UserID) bool {
|
||||
for _, authorizedUserID := range endpoint.AuthorizedUsers {
|
||||
if authorizedUserID == userID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
memberships, _ := handler.TeamMembershipService.TeamMembershipsByUserID(userID)
|
||||
for _, authorizedTeamID := range endpoint.AuthorizedTeams {
|
||||
for _, membership := range memberships {
|
||||
if membership.TeamID == authorizedTeamID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (handler *DockerHandler) proxyRequestsToDockerAPI(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
id := vars["id"]
|
||||
|
@ -75,7 +57,14 @@ func (handler *DockerHandler) proxyRequestsToDockerAPI(w http.ResponseWriter, r
|
|||
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
||||
return
|
||||
}
|
||||
if tokenData.Role != portainer.AdministratorRole && !handler.checkEndpointAccessControl(endpoint, tokenData.ID) {
|
||||
|
||||
memberships, err := handler.TeamMembershipService.TeamMembershipsByUserID(tokenData.ID)
|
||||
if err != nil {
|
||||
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
if tokenData.Role != portainer.AdministratorRole && !security.AuthorizedEndpointAccess(endpoint, tokenData.ID, memberships) {
|
||||
httperror.WriteErrorResponse(w, portainer.ErrEndpointAccessDenied, http.StatusForbidden, handler.Logger)
|
||||
return
|
||||
}
|
||||
|
@ -85,7 +74,7 @@ func (handler *DockerHandler) proxyRequestsToDockerAPI(w http.ResponseWriter, r
|
|||
if proxy == nil {
|
||||
proxy, err = handler.ProxyManager.CreateAndRegisterProxy(endpoint)
|
||||
if err != nil {
|
||||
httperror.WriteErrorResponse(w, err, http.StatusBadRequest, handler.Logger)
|
||||
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -136,6 +136,7 @@ func (handler *EndpointHandler) handlePostEndpoints(w http.ResponseWriter, r *ht
|
|||
},
|
||||
AuthorizedUsers: []portainer.UserID{},
|
||||
AuthorizedTeams: []portainer.TeamID{},
|
||||
Extensions: []portainer.EndpointExtension{},
|
||||
}
|
||||
|
||||
err = handler.EndpointService.CreateEndpoint(endpoint)
|
||||
|
@ -372,6 +373,7 @@ func (handler *EndpointHandler) handleDeleteEndpoint(w http.ResponseWriter, r *h
|
|||
}
|
||||
|
||||
handler.ProxyManager.DeleteProxy(string(endpointID))
|
||||
handler.ProxyManager.DeleteExtensionProxies(string(endpointID))
|
||||
|
||||
err = handler.EndpointService.DeleteEndpoint(portainer.EndpointID(endpointID))
|
||||
if err != nil {
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/proxy"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// ExtensionHandler represents an HTTP API handler for managing Settings.
|
||||
type ExtensionHandler struct {
|
||||
*mux.Router
|
||||
Logger *log.Logger
|
||||
EndpointService portainer.EndpointService
|
||||
ProxyManager *proxy.Manager
|
||||
}
|
||||
|
||||
// NewExtensionHandler returns a new instance of ExtensionHandler.
|
||||
func NewExtensionHandler(bouncer *security.RequestBouncer) *ExtensionHandler {
|
||||
h := &ExtensionHandler{
|
||||
Router: mux.NewRouter(),
|
||||
Logger: log.New(os.Stderr, "", log.LstdFlags),
|
||||
}
|
||||
h.Handle("/{endpointId}/extensions",
|
||||
bouncer.AdministratorAccess(http.HandlerFunc(h.handlePostExtensions))).Methods(http.MethodPost)
|
||||
return h
|
||||
}
|
||||
|
||||
type (
|
||||
postExtensionRequest struct {
|
||||
Type int `valid:"required"`
|
||||
URL string `valid:"required"`
|
||||
}
|
||||
)
|
||||
|
||||
func (handler *ExtensionHandler) handlePostExtensions(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
id, err := strconv.Atoi(vars["endpointId"])
|
||||
if err != nil {
|
||||
httperror.WriteErrorResponse(w, err, http.StatusBadRequest, handler.Logger)
|
||||
return
|
||||
}
|
||||
endpointID := portainer.EndpointID(id)
|
||||
|
||||
endpoint, err := handler.EndpointService.Endpoint(endpointID)
|
||||
if err == portainer.ErrEndpointNotFound {
|
||||
httperror.WriteErrorResponse(w, err, http.StatusNotFound, handler.Logger)
|
||||
return
|
||||
} else if err != nil {
|
||||
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
var req postExtensionRequest
|
||||
if err = json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
httperror.WriteErrorResponse(w, ErrInvalidJSON, http.StatusBadRequest, handler.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = govalidator.ValidateStruct(req)
|
||||
if err != nil {
|
||||
httperror.WriteErrorResponse(w, ErrInvalidRequestFormat, http.StatusBadRequest, handler.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
extensionType := portainer.EndpointExtensionType(req.Type)
|
||||
|
||||
for _, extension := range endpoint.Extensions {
|
||||
if extension.Type == extensionType {
|
||||
httperror.WriteErrorResponse(w, portainer.ErrEndpointExtensionAlreadyAssociated, http.StatusConflict, handler.Logger)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
extension := portainer.EndpointExtension{
|
||||
Type: extensionType,
|
||||
URL: req.URL,
|
||||
}
|
||||
|
||||
endpoint.Extensions = append(endpoint.Extensions, extension)
|
||||
|
||||
err = handler.EndpointService.UpdateEndpoint(endpoint.ID, endpoint)
|
||||
if err != nil {
|
||||
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
encodeJSON(w, extension, handler.Logger)
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
package extensions
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/proxy"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// StoridgeHandler represents an HTTP API handler for proxying requests to the Docker API.
|
||||
type StoridgeHandler struct {
|
||||
*mux.Router
|
||||
Logger *log.Logger
|
||||
EndpointService portainer.EndpointService
|
||||
TeamMembershipService portainer.TeamMembershipService
|
||||
ProxyManager *proxy.Manager
|
||||
}
|
||||
|
||||
// NewStoridgeHandler returns a new instance of StoridgeHandler.
|
||||
func NewStoridgeHandler(bouncer *security.RequestBouncer) *StoridgeHandler {
|
||||
h := &StoridgeHandler{
|
||||
Router: mux.NewRouter(),
|
||||
Logger: log.New(os.Stderr, "", log.LstdFlags),
|
||||
}
|
||||
h.PathPrefix("/{id}/extensions/storidge").Handler(
|
||||
bouncer.AuthenticatedAccess(http.HandlerFunc(h.proxyRequestsToStoridgeAPI)))
|
||||
return h
|
||||
}
|
||||
|
||||
func (handler *StoridgeHandler) proxyRequestsToStoridgeAPI(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
id := vars["id"]
|
||||
|
||||
parsedID, err := strconv.Atoi(id)
|
||||
if err != nil {
|
||||
httperror.WriteErrorResponse(w, err, http.StatusBadRequest, handler.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
endpointID := portainer.EndpointID(parsedID)
|
||||
endpoint, err := handler.EndpointService.Endpoint(endpointID)
|
||||
if err != nil {
|
||||
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
tokenData, err := security.RetrieveTokenData(r)
|
||||
if err != nil {
|
||||
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
memberships, err := handler.TeamMembershipService.TeamMembershipsByUserID(tokenData.ID)
|
||||
if err != nil {
|
||||
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
if tokenData.Role != portainer.AdministratorRole && !security.AuthorizedEndpointAccess(endpoint, tokenData.ID, memberships) {
|
||||
httperror.WriteErrorResponse(w, portainer.ErrEndpointAccessDenied, http.StatusForbidden, handler.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
var storidgeExtension *portainer.EndpointExtension
|
||||
for _, extension := range endpoint.Extensions {
|
||||
if extension.Type == portainer.StoridgeEndpointExtension {
|
||||
storidgeExtension = &extension
|
||||
}
|
||||
}
|
||||
|
||||
if storidgeExtension == nil {
|
||||
httperror.WriteErrorResponse(w, portainer.ErrEndpointExtensionNotSupported, http.StatusInternalServerError, handler.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
proxyExtensionKey := string(endpoint.ID) + "_" + string(portainer.StoridgeEndpointExtension)
|
||||
|
||||
var proxy http.Handler
|
||||
proxy = handler.ProxyManager.GetExtensionProxy(proxyExtensionKey)
|
||||
if proxy == nil {
|
||||
proxy, err = handler.ProxyManager.CreateAndRegisterExtensionProxy(proxyExtensionKey, storidgeExtension.URL)
|
||||
if err != nil {
|
||||
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
http.StripPrefix("/"+id+"/extensions/storidge", proxy).ServeHTTP(w, r)
|
||||
}
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/handler/extensions"
|
||||
)
|
||||
|
||||
// Handler is a collection of all the service handlers.
|
||||
|
@ -19,6 +20,8 @@ type Handler struct {
|
|||
EndpointHandler *EndpointHandler
|
||||
RegistryHandler *RegistryHandler
|
||||
DockerHubHandler *DockerHubHandler
|
||||
ExtensionHandler *ExtensionHandler
|
||||
StoridgeHandler *extensions.StoridgeHandler
|
||||
ResourceHandler *ResourceHandler
|
||||
StackHandler *StackHandler
|
||||
StatusHandler *StatusHandler
|
||||
|
@ -48,11 +51,16 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
case strings.HasPrefix(r.URL.Path, "/api/dockerhub"):
|
||||
http.StripPrefix("/api", h.DockerHubHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/endpoints"):
|
||||
if strings.Contains(r.URL.Path, "/docker/") {
|
||||
switch {
|
||||
case strings.Contains(r.URL.Path, "/docker"):
|
||||
http.StripPrefix("/api/endpoints", h.DockerHandler).ServeHTTP(w, r)
|
||||
} else if strings.Contains(r.URL.Path, "/stacks") {
|
||||
case strings.Contains(r.URL.Path, "/stacks"):
|
||||
http.StripPrefix("/api/endpoints", h.StackHandler).ServeHTTP(w, r)
|
||||
} else {
|
||||
case strings.Contains(r.URL.Path, "/extensions/storidge"):
|
||||
http.StripPrefix("/api/endpoints", h.StoridgeHandler).ServeHTTP(w, r)
|
||||
case strings.Contains(r.URL.Path, "/extensions"):
|
||||
http.StripPrefix("/api/endpoints", h.ExtensionHandler).ServeHTTP(w, r)
|
||||
default:
|
||||
http.StripPrefix("/api", h.EndpointHandler).ServeHTTP(w, r)
|
||||
}
|
||||
case strings.HasPrefix(r.URL.Path, "/api/registries"):
|
||||
|
|
|
@ -17,14 +17,14 @@ type proxyFactory struct {
|
|||
SettingsService portainer.SettingsService
|
||||
}
|
||||
|
||||
func (factory *proxyFactory) newHTTPProxy(u *url.URL) http.Handler {
|
||||
func (factory *proxyFactory) newExtensionHTTPPRoxy(u *url.URL) http.Handler {
|
||||
u.Scheme = "http"
|
||||
return factory.createReverseProxy(u)
|
||||
return newSingleHostReverseProxyWithHostHeader(u)
|
||||
}
|
||||
|
||||
func (factory *proxyFactory) newHTTPSProxy(u *url.URL, endpoint *portainer.Endpoint) (http.Handler, error) {
|
||||
func (factory *proxyFactory) newDockerHTTPSProxy(u *url.URL, endpoint *portainer.Endpoint) (http.Handler, error) {
|
||||
u.Scheme = "https"
|
||||
proxy := factory.createReverseProxy(u)
|
||||
proxy := factory.createDockerReverseProxy(u)
|
||||
config, err := crypto.CreateTLSConfiguration(&endpoint.TLSConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -34,7 +34,12 @@ func (factory *proxyFactory) newHTTPSProxy(u *url.URL, endpoint *portainer.Endpo
|
|||
return proxy, nil
|
||||
}
|
||||
|
||||
func (factory *proxyFactory) newSocketProxy(path string) http.Handler {
|
||||
func (factory *proxyFactory) newDockerHTTPProxy(u *url.URL) http.Handler {
|
||||
u.Scheme = "http"
|
||||
return factory.createDockerReverseProxy(u)
|
||||
}
|
||||
|
||||
func (factory *proxyFactory) newDockerSocketProxy(path string) http.Handler {
|
||||
proxy := &socketProxy{}
|
||||
transport := &proxyTransport{
|
||||
ResourceControlService: factory.ResourceControlService,
|
||||
|
@ -46,13 +51,13 @@ func (factory *proxyFactory) newSocketProxy(path string) http.Handler {
|
|||
return proxy
|
||||
}
|
||||
|
||||
func (factory *proxyFactory) createReverseProxy(u *url.URL) *httputil.ReverseProxy {
|
||||
func (factory *proxyFactory) createDockerReverseProxy(u *url.URL) *httputil.ReverseProxy {
|
||||
proxy := newSingleHostReverseProxyWithHostHeader(u)
|
||||
transport := &proxyTransport{
|
||||
ResourceControlService: factory.ResourceControlService,
|
||||
TeamMembershipService: factory.TeamMembershipService,
|
||||
SettingsService: factory.SettingsService,
|
||||
dockerTransport: newHTTPTransport(),
|
||||
dockerTransport: &http.Transport{},
|
||||
}
|
||||
proxy.Transport = transport
|
||||
return proxy
|
||||
|
@ -65,7 +70,3 @@ func newSocketTransport(socketPath string) *http.Transport {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newHTTPTransport() *http.Transport {
|
||||
return &http.Transport{}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package proxy
|
|||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/orcaman/concurrent-map"
|
||||
"github.com/portainer/portainer"
|
||||
|
@ -10,14 +11,16 @@ import (
|
|||
|
||||
// Manager represents a service used to manage Docker proxies.
|
||||
type Manager struct {
|
||||
proxyFactory *proxyFactory
|
||||
proxies cmap.ConcurrentMap
|
||||
proxyFactory *proxyFactory
|
||||
proxies cmap.ConcurrentMap
|
||||
extensionProxies cmap.ConcurrentMap
|
||||
}
|
||||
|
||||
// NewManager initializes a new proxy Service
|
||||
func NewManager(resourceControlService portainer.ResourceControlService, teamMembershipService portainer.TeamMembershipService, settingsService portainer.SettingsService) *Manager {
|
||||
return &Manager{
|
||||
proxies: cmap.New(),
|
||||
proxies: cmap.New(),
|
||||
extensionProxies: cmap.New(),
|
||||
proxyFactory: &proxyFactory{
|
||||
ResourceControlService: resourceControlService,
|
||||
TeamMembershipService: teamMembershipService,
|
||||
|
@ -38,16 +41,16 @@ func (manager *Manager) CreateAndRegisterProxy(endpoint *portainer.Endpoint) (ht
|
|||
|
||||
if endpointURL.Scheme == "tcp" {
|
||||
if endpoint.TLSConfig.TLS {
|
||||
proxy, err = manager.proxyFactory.newHTTPSProxy(endpointURL, endpoint)
|
||||
proxy, err = manager.proxyFactory.newDockerHTTPSProxy(endpointURL, endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
proxy = manager.proxyFactory.newHTTPProxy(endpointURL)
|
||||
proxy = manager.proxyFactory.newDockerHTTPProxy(endpointURL)
|
||||
}
|
||||
} else {
|
||||
// Assume unix:// scheme
|
||||
proxy = manager.proxyFactory.newSocketProxy(endpointURL.Path)
|
||||
proxy = manager.proxyFactory.newDockerSocketProxy(endpointURL.Path)
|
||||
}
|
||||
|
||||
manager.proxies.Set(string(endpoint.ID), proxy)
|
||||
|
@ -67,3 +70,34 @@ func (manager *Manager) GetProxy(key string) http.Handler {
|
|||
func (manager *Manager) DeleteProxy(key string) {
|
||||
manager.proxies.Remove(key)
|
||||
}
|
||||
|
||||
// CreateAndRegisterExtensionProxy creates a new HTTP reverse proxy for an extension and adds it to the registered proxies.
|
||||
func (manager *Manager) CreateAndRegisterExtensionProxy(key, extensionAPIURL string) (http.Handler, error) {
|
||||
|
||||
extensionURL, err := url.Parse(extensionAPIURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proxy := manager.proxyFactory.newExtensionHTTPPRoxy(extensionURL)
|
||||
manager.extensionProxies.Set(key, proxy)
|
||||
return proxy, nil
|
||||
}
|
||||
|
||||
// GetExtensionProxy returns the extension proxy associated to a key
|
||||
func (manager *Manager) GetExtensionProxy(key string) http.Handler {
|
||||
proxy, ok := manager.extensionProxies.Get(key)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return proxy.(http.Handler)
|
||||
}
|
||||
|
||||
// DeleteExtensionProxies deletes all the extension proxies associated to a key
|
||||
func (manager *Manager) DeleteExtensionProxies(key string) {
|
||||
for _, k := range manager.extensionProxies.Keys() {
|
||||
if strings.Contains(k, key+"_") {
|
||||
manager.extensionProxies.Remove(k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -121,3 +121,22 @@ func AuthorizedUserManagement(userID portainer.UserID, context *RestrictedReques
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AuthorizedEndpointAccess ensure that the user can access the specified endpoint.
|
||||
// It will check if the user is part of the authorized users or part of a team that is
|
||||
// listed in the authorized teams.
|
||||
func AuthorizedEndpointAccess(endpoint *portainer.Endpoint, userID portainer.UserID, memberships []portainer.TeamMembership) bool {
|
||||
for _, authorizedUserID := range endpoint.AuthorizedUsers {
|
||||
if authorizedUserID == userID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, membership := range memberships {
|
||||
for _, authorizedTeamID := range endpoint.AuthorizedTeams {
|
||||
if membership.TeamID == authorizedTeamID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package http
|
|||
import (
|
||||
"github.com/portainer/portainer"
|
||||
"github.com/portainer/portainer/http/handler"
|
||||
"github.com/portainer/portainer/http/handler/extensions"
|
||||
"github.com/portainer/portainer/http/proxy"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
|
||||
|
@ -96,6 +97,13 @@ func (server *Server) Start() error {
|
|||
stackHandler.GitService = server.GitService
|
||||
stackHandler.RegistryService = server.RegistryService
|
||||
stackHandler.DockerHubService = server.DockerHubService
|
||||
var extensionHandler = handler.NewExtensionHandler(requestBouncer)
|
||||
extensionHandler.EndpointService = server.EndpointService
|
||||
extensionHandler.ProxyManager = proxyManager
|
||||
var storidgeHandler = extensions.NewStoridgeHandler(requestBouncer)
|
||||
storidgeHandler.EndpointService = server.EndpointService
|
||||
storidgeHandler.TeamMembershipService = server.TeamMembershipService
|
||||
storidgeHandler.ProxyManager = proxyManager
|
||||
|
||||
server.Handler = &handler.Handler{
|
||||
AuthHandler: authHandler,
|
||||
|
@ -114,6 +122,8 @@ func (server *Server) Start() error {
|
|||
WebSocketHandler: websocketHandler,
|
||||
FileHandler: fileHandler,
|
||||
UploadHandler: uploadHandler,
|
||||
ExtensionHandler: extensionHandler,
|
||||
StoridgeHandler: storidgeHandler,
|
||||
}
|
||||
|
||||
if server.SSL {
|
||||
|
|
|
@ -171,13 +171,14 @@ type (
|
|||
// Endpoint represents a Docker endpoint with all the info required
|
||||
// to connect to it.
|
||||
Endpoint struct {
|
||||
ID EndpointID `json:"Id"`
|
||||
Name string `json:"Name"`
|
||||
URL string `json:"URL"`
|
||||
PublicURL string `json:"PublicURL"`
|
||||
TLSConfig TLSConfiguration `json:"TLSConfig"`
|
||||
AuthorizedUsers []UserID `json:"AuthorizedUsers"`
|
||||
AuthorizedTeams []TeamID `json:"AuthorizedTeams"`
|
||||
ID EndpointID `json:"Id"`
|
||||
Name string `json:"Name"`
|
||||
URL string `json:"URL"`
|
||||
PublicURL string `json:"PublicURL"`
|
||||
TLSConfig TLSConfiguration `json:"TLSConfig"`
|
||||
AuthorizedUsers []UserID `json:"AuthorizedUsers"`
|
||||
AuthorizedTeams []TeamID `json:"AuthorizedTeams"`
|
||||
Extensions []EndpointExtension `json:"Extensions"`
|
||||
|
||||
// Deprecated fields
|
||||
// Deprecated in DBVersion == 4
|
||||
|
@ -187,6 +188,16 @@ type (
|
|||
TLSKeyPath string `json:"TLSKey,omitempty"`
|
||||
}
|
||||
|
||||
// EndpointExtension represents a extension associated to an endpoint.
|
||||
EndpointExtension struct {
|
||||
Type EndpointExtensionType `json:"Type"`
|
||||
URL string `json:"URL"`
|
||||
}
|
||||
|
||||
// EndpointExtensionType represents the type of an endpoint extension. Only
|
||||
// one extension of each type can be associated to an endpoint.
|
||||
EndpointExtensionType int
|
||||
|
||||
// ResourceControlID represents a resource control identifier.
|
||||
ResourceControlID int
|
||||
|
||||
|
@ -391,7 +402,7 @@ const (
|
|||
// APIVersion is the version number of the Portainer API.
|
||||
APIVersion = "1.16.2"
|
||||
// DBVersion is the version number of the Portainer database.
|
||||
DBVersion = 7
|
||||
DBVersion = 8
|
||||
// DefaultTemplatesURL represents the default URL for the templates definitions.
|
||||
DefaultTemplatesURL = "https://raw.githubusercontent.com/portainer/templates/master/templates.json"
|
||||
)
|
||||
|
@ -452,3 +463,9 @@ const (
|
|||
// ConfigResourceControl represents a resource control associated to a Docker config
|
||||
ConfigResourceControl
|
||||
)
|
||||
|
||||
const (
|
||||
_ EndpointExtensionType = iota
|
||||
// StoridgeEndpointExtension represents the Storidge extension
|
||||
StoridgeEndpointExtension
|
||||
)
|
||||
|
|
|
@ -7,7 +7,7 @@ angular.module('portainer.docker')
|
|||
},
|
||||
{
|
||||
info: { method: 'GET', params: { action: 'info' }, ignoreLoadingBar: true },
|
||||
version: { method: 'GET', params: { action: 'version' }, ignoreLoadingBar: true },
|
||||
version: { method: 'GET', params: { action: 'version' }, ignoreLoadingBar: true, timeout: 4500 },
|
||||
events: {
|
||||
method: 'GET', params: { action: 'events', since: '@since', until: '@until' },
|
||||
isArray: true, transformResponse: jsonObjectsToArrayHandler
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
angular.module('portainer.docker')
|
||||
.controller('CreateVolumeController', ['$q', '$scope', '$state', 'VolumeService', 'PluginService', 'ResourceControlService', 'Authentication', 'Notifications', 'FormValidator', 'ExtensionManager',
|
||||
function ($q, $scope, $state, VolumeService, PluginService, ResourceControlService, Authentication, Notifications, FormValidator, ExtensionManager) {
|
||||
.controller('CreateVolumeController', ['$q', '$scope', '$state', 'VolumeService', 'PluginService', 'ResourceControlService', 'Authentication', 'Notifications', 'FormValidator',
|
||||
function ($q, $scope, $state, VolumeService, PluginService, ResourceControlService, Authentication, Notifications, FormValidator) {
|
||||
|
||||
$scope.formValues = {
|
||||
Driver: 'local',
|
||||
|
@ -88,11 +88,5 @@ function ($q, $scope, $state, VolumeService, PluginService, ResourceControlServi
|
|||
}
|
||||
}
|
||||
|
||||
ExtensionManager.init()
|
||||
.then(function success(data) {
|
||||
initView();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to initialize extensions');
|
||||
});
|
||||
initView();
|
||||
}]);
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
angular.module('extension.storidge')
|
||||
.factory('StoridgeCluster', ['$http', 'StoridgeManager', function StoridgeClusterFactory($http, StoridgeManager) {
|
||||
'use strict';
|
||||
|
||||
var service = {};
|
||||
|
||||
service.queryEvents = function() {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: StoridgeManager.StoridgeAPIURL() + '/events',
|
||||
skipAuthorization: true,
|
||||
timeout: 4500,
|
||||
ignoreLoadingBar: true
|
||||
});
|
||||
};
|
||||
|
||||
service.queryVersion = function() {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: StoridgeManager.StoridgeAPIURL() + '/version',
|
||||
skipAuthorization: true
|
||||
});
|
||||
};
|
||||
|
||||
service.queryInfo = function() {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: StoridgeManager.StoridgeAPIURL() + '/info',
|
||||
skipAuthorization: true,
|
||||
timeout: 4500,
|
||||
ignoreLoadingBar: true
|
||||
});
|
||||
};
|
||||
|
||||
service.reboot = function() {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: StoridgeManager.StoridgeAPIURL() + '/cluster/reboot',
|
||||
skipAuthorization: true
|
||||
});
|
||||
};
|
||||
|
||||
service.shutdown = function() {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: StoridgeManager.StoridgeAPIURL() + '/cluster/shutdown',
|
||||
skipAuthorization: true
|
||||
});
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
|
@ -1,24 +0,0 @@
|
|||
angular.module('extension.storidge')
|
||||
.factory('StoridgeNodes', ['$http', 'StoridgeManager', function StoridgeNodesFactory($http, StoridgeManager) {
|
||||
'use strict';
|
||||
|
||||
var service = {};
|
||||
|
||||
service.query = function() {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: StoridgeManager.StoridgeAPIURL() + '/nodes',
|
||||
skipAuthorization: true
|
||||
});
|
||||
};
|
||||
|
||||
service.inspect = function(id) {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: StoridgeManager.StoridgeAPIURL() + '/nodes/' + id,
|
||||
skipAuthorization: true
|
||||
});
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
|
@ -1,52 +0,0 @@
|
|||
angular.module('extension.storidge')
|
||||
.factory('StoridgeProfiles', ['$http', 'StoridgeManager', function StoridgeProfilesFactory($http, StoridgeManager) {
|
||||
'use strict';
|
||||
|
||||
var service = {};
|
||||
|
||||
service.create = function(payload) {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: StoridgeManager.StoridgeAPIURL() + '/profiles',
|
||||
data: payload,
|
||||
headers: { 'Content-type': 'application/json' },
|
||||
skipAuthorization: true
|
||||
});
|
||||
};
|
||||
|
||||
service.update = function(id, payload) {
|
||||
return $http({
|
||||
method: 'PUT',
|
||||
url: StoridgeManager.StoridgeAPIURL() + '/profiles/' + id,
|
||||
data: payload,
|
||||
headers: { 'Content-type': 'application/json' },
|
||||
skipAuthorization: true
|
||||
});
|
||||
};
|
||||
|
||||
service.query = function() {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: StoridgeManager.StoridgeAPIURL() + '/profiles',
|
||||
skipAuthorization: true
|
||||
});
|
||||
};
|
||||
|
||||
service.inspect = function(id) {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: StoridgeManager.StoridgeAPIURL() + '/profiles/' + id,
|
||||
skipAuthorization: true
|
||||
});
|
||||
};
|
||||
|
||||
service.delete = function(id) {
|
||||
return $http({
|
||||
method: 'DELETE',
|
||||
url: StoridgeManager.StoridgeAPIURL() + '/profiles/' + id,
|
||||
skipAuthorization: true
|
||||
});
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
|
@ -0,0 +1,20 @@
|
|||
angular.module('extension.storidge')
|
||||
.factory('Storidge', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', function StoridgeFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
||||
'use strict';
|
||||
return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/extensions/storidge/:resource/:id/:action', {
|
||||
endpointId: EndpointProvider.endpointID
|
||||
},
|
||||
{
|
||||
rebootCluster: { method: 'POST', params: { resource: 'cluster', action: 'reboot' } },
|
||||
shutdownCluster: { method: 'POST', params: { resource: 'cluster', action: 'shutdown' } },
|
||||
queryEvents: { method: 'GET', params: { resource: 'events' }, timeout: 4500, ignoreLoadingBar: true, isArray: true },
|
||||
getVersion: { method: 'GET', params: { resource: 'version' } },
|
||||
getInfo: { method: 'GET', params: { resource: 'info' }, timeout: 4500, ignoreLoadingBar: true },
|
||||
queryNodes: { method: 'GET', params: { resource: 'nodes' } },
|
||||
queryProfiles: { method: 'GET', params: { resource: 'profiles' } },
|
||||
getProfile: { method: 'GET', params: { resource: 'profiles' } },
|
||||
createProfile: { method: 'POST', params: { resource: 'profiles' } },
|
||||
updateProfile: { method: 'PUT', params: { resource: 'profiles', id: '@name' } },
|
||||
deleteProfile: { method: 'DELETE', params: { resource: 'profiles' } }
|
||||
});
|
||||
}]);
|
|
@ -1,22 +1,22 @@
|
|||
angular.module('extension.storidge')
|
||||
.factory('StoridgeClusterService', ['$q', 'StoridgeCluster', function StoridgeClusterServiceFactory($q, StoridgeCluster) {
|
||||
.factory('StoridgeClusterService', ['$q', 'Storidge', function StoridgeClusterServiceFactory($q, Storidge) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.reboot = function() {
|
||||
return StoridgeCluster.reboot();
|
||||
return Storidge.rebootCluster().$promise;
|
||||
};
|
||||
|
||||
service.shutdown = function() {
|
||||
return StoridgeCluster.shutdown();
|
||||
return Storidge.shutdownCluster().$promise;
|
||||
};
|
||||
|
||||
service.info = function() {
|
||||
var deferred = $q.defer();
|
||||
|
||||
StoridgeCluster.queryInfo()
|
||||
.then(function success(response) {
|
||||
var info = new StoridgeInfoModel(response.data);
|
||||
Storidge.getInfo().$promise
|
||||
.then(function success(data) {
|
||||
var info = new StoridgeInfoModel(data);
|
||||
deferred.resolve(info);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
|
@ -29,9 +29,9 @@ angular.module('extension.storidge')
|
|||
service.version = function() {
|
||||
var deferred = $q.defer();
|
||||
|
||||
StoridgeCluster.queryVersion()
|
||||
.then(function success(response) {
|
||||
var version = response.data.version;
|
||||
Storidge.getVersion().$promise
|
||||
.then(function success(data) {
|
||||
var version = data.version;
|
||||
deferred.resolve(version);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
|
@ -44,9 +44,9 @@ angular.module('extension.storidge')
|
|||
service.events = function() {
|
||||
var deferred = $q.defer();
|
||||
|
||||
StoridgeCluster.queryEvents()
|
||||
.then(function success(response) {
|
||||
var events = response.data.map(function(item) {
|
||||
Storidge.queryEvents().$promise
|
||||
.then(function success(data) {
|
||||
var events = data.map(function(item) {
|
||||
return new StoridgeEventModel(item);
|
||||
});
|
||||
deferred.resolve(events);
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
angular.module('extension.storidge')
|
||||
.factory('StoridgeManager', ['$q', 'LocalStorage', 'SystemService', function StoridgeManagerFactory($q, LocalStorage, SystemService) {
|
||||
'use strict';
|
||||
var service = {
|
||||
API: ''
|
||||
};
|
||||
|
||||
service.init = function() {
|
||||
var deferred = $q.defer();
|
||||
|
||||
var storedAPIURL = LocalStorage.getStoridgeAPIURL();
|
||||
if (storedAPIURL) {
|
||||
service.API = storedAPIURL;
|
||||
deferred.resolve();
|
||||
} else {
|
||||
SystemService.info()
|
||||
.then(function success(data) {
|
||||
var endpointAddress = LocalStorage.getEndpointPublicURL();
|
||||
var storidgeAPIURL = '';
|
||||
if (endpointAddress) {
|
||||
storidgeAPIURL = 'http://' + endpointAddress + ':8282';
|
||||
} else {
|
||||
var managerIP = data.Swarm.NodeAddr;
|
||||
storidgeAPIURL = 'http://' + managerIP + ':8282';
|
||||
}
|
||||
|
||||
service.API = storidgeAPIURL;
|
||||
LocalStorage.storeStoridgeAPIURL(storidgeAPIURL);
|
||||
deferred.resolve();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve Storidge API URL', err: err });
|
||||
});
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.reset = function() {
|
||||
LocalStorage.clearStoridgeAPIURL();
|
||||
};
|
||||
|
||||
service.StoridgeAPIURL = function() {
|
||||
return service.API;
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
|
@ -1,14 +1,14 @@
|
|||
angular.module('extension.storidge')
|
||||
.factory('StoridgeNodeService', ['$q', 'StoridgeNodes', function StoridgeNodeServiceFactory($q, StoridgeNodes) {
|
||||
.factory('StoridgeNodeService', ['$q', 'Storidge', function StoridgeNodeServiceFactory($q, Storidge) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.nodes = function() {
|
||||
var deferred = $q.defer();
|
||||
|
||||
StoridgeNodes.query()
|
||||
.then(function success(response) {
|
||||
var nodeData = response.data.nodes;
|
||||
Storidge.queryNodes().$promise
|
||||
.then(function success(data) {
|
||||
var nodeData = data.nodes;
|
||||
var nodes = [];
|
||||
|
||||
for (var key in nodeData) {
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
angular.module('extension.storidge')
|
||||
.factory('StoridgeProfileService', ['$q', 'StoridgeProfiles', function StoridgeProfileServiceFactory($q, StoridgeProfiles) {
|
||||
.factory('StoridgeProfileService', ['$q', 'Storidge', function StoridgeProfileServiceFactory($q, Storidge) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.create = function(model) {
|
||||
var payload = new StoridgeCreateProfileRequest(model);
|
||||
return StoridgeProfiles.create(payload);
|
||||
return Storidge.createProfile(payload).$promise;
|
||||
};
|
||||
|
||||
service.update = function(model) {
|
||||
var payload = new StoridgeCreateProfileRequest(model);
|
||||
return StoridgeProfiles.update(model.Name, payload);
|
||||
return Storidge.updateProfile(payload).$promise;
|
||||
};
|
||||
|
||||
service.delete = function(profileName) {
|
||||
return StoridgeProfiles.delete(profileName);
|
||||
return Storidge.deleteProfile({ id: profileName }).$promise;
|
||||
};
|
||||
|
||||
service.profile = function(profileName) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
StoridgeProfiles.inspect(profileName)
|
||||
.then(function success(response) {
|
||||
var profile = new StoridgeProfileModel(profileName, response.data);
|
||||
Storidge.getProfile({ id: profileName }).$promise
|
||||
.then(function success(data) {
|
||||
var profile = new StoridgeProfileModel(profileName, data);
|
||||
deferred.resolve(profile);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
|
@ -35,9 +35,9 @@ angular.module('extension.storidge')
|
|||
service.profiles = function() {
|
||||
var deferred = $q.defer();
|
||||
|
||||
StoridgeProfiles.query()
|
||||
.then(function success(response) {
|
||||
var profiles = response.data.profiles.map(function (item) {
|
||||
Storidge.queryProfiles().$promise
|
||||
.then(function success(data) {
|
||||
var profiles = data.profiles.map(function (item) {
|
||||
return new StoridgeProfileListModel(item);
|
||||
});
|
||||
deferred.resolve(profiles);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
angular.module('extension.storidge')
|
||||
.controller('StoridgeClusterController', ['$q', '$scope', '$state', 'Notifications', 'StoridgeClusterService', 'StoridgeNodeService', 'StoridgeManager', 'ModalService',
|
||||
function ($q, $scope, $state, Notifications, StoridgeClusterService, StoridgeNodeService, StoridgeManager, ModalService) {
|
||||
.controller('StoridgeClusterController', ['$q', '$scope', '$state', 'Notifications', 'StoridgeClusterService', 'StoridgeNodeService', 'ModalService',
|
||||
function ($q, $scope, $state, Notifications, StoridgeClusterService, StoridgeNodeService, ModalService) {
|
||||
|
||||
$scope.state = {
|
||||
shutdownInProgress: false,
|
||||
|
@ -44,30 +44,20 @@ function ($q, $scope, $state, Notifications, StoridgeClusterService, StoridgeNod
|
|||
function shutdownCluster() {
|
||||
$scope.state.shutdownInProgress = true;
|
||||
StoridgeClusterService.shutdown()
|
||||
.then(function success(data) {
|
||||
Notifications.success('Cluster successfully shutdown');
|
||||
$state.go('docker.dashboard');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to shutdown cluster');
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.shutdownInProgress = false;
|
||||
Notifications.success('Cluster successfully shutdown');
|
||||
$state.go('docker.dashboard');
|
||||
});
|
||||
}
|
||||
|
||||
function rebootCluster() {
|
||||
$scope.state.rebootInProgress = true;
|
||||
StoridgeClusterService.reboot()
|
||||
.then(function success(data) {
|
||||
Notifications.success('Cluster successfully rebooted');
|
||||
$state.reload();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to reboot cluster');
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.rebootInProgress = false;
|
||||
Notifications.success('Cluster successfully rebooted');
|
||||
$state.reload();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -87,11 +77,5 @@ function ($q, $scope, $state, Notifications, StoridgeClusterService, StoridgeNod
|
|||
});
|
||||
}
|
||||
|
||||
StoridgeManager.init()
|
||||
.then(function success() {
|
||||
initView();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to communicate with Storidge API');
|
||||
});
|
||||
initView();
|
||||
}]);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
angular.module('extension.storidge')
|
||||
.controller('StoridgeMonitorController', ['$q', '$scope', '$interval', '$document', 'Notifications', 'StoridgeClusterService', 'StoridgeChartService', 'StoridgeManager', 'ModalService',
|
||||
function ($q, $scope, $interval, $document, Notifications, StoridgeClusterService, StoridgeChartService, StoridgeManager, ModalService) {
|
||||
.controller('StoridgeMonitorController', ['$q', '$scope', '$interval', '$document', 'Notifications', 'StoridgeClusterService', 'StoridgeChartService', 'ModalService',
|
||||
function ($q, $scope, $interval, $document, Notifications, StoridgeClusterService, StoridgeChartService, ModalService) {
|
||||
|
||||
$scope.$on('$destroy', function() {
|
||||
stopRepeater();
|
||||
|
@ -98,11 +98,5 @@ function ($q, $scope, $interval, $document, Notifications, StoridgeClusterServic
|
|||
});
|
||||
}
|
||||
|
||||
StoridgeManager.init()
|
||||
.then(function success() {
|
||||
initView();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to communicate with Storidge API');
|
||||
});
|
||||
initView();
|
||||
}]);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
angular.module('extension.storidge')
|
||||
.controller('StoridgeCreateProfileController', ['$scope', '$state', '$transition$', 'Notifications', 'StoridgeProfileService', 'StoridgeManager',
|
||||
function ($scope, $state, $transition$, Notifications, StoridgeProfileService, StoridgeManager) {
|
||||
.controller('StoridgeCreateProfileController', ['$scope', '$state', '$transition$', 'Notifications', 'StoridgeProfileService',
|
||||
function ($scope, $state, $transition$, Notifications, StoridgeProfileService) {
|
||||
|
||||
$scope.state = {
|
||||
NoLimit: true,
|
||||
|
@ -62,11 +62,5 @@ function ($scope, $state, $transition$, Notifications, StoridgeProfileService, S
|
|||
$scope.model = profile;
|
||||
}
|
||||
|
||||
StoridgeManager.init()
|
||||
.then(function success() {
|
||||
initView();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to communicate with Storidge API');
|
||||
});
|
||||
initView();
|
||||
}]);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
angular.module('extension.storidge')
|
||||
.controller('StoridgeProfileController', ['$scope', '$state', '$transition$', 'Notifications', 'StoridgeProfileService', 'StoridgeManager', 'ModalService',
|
||||
function ($scope, $state, $transition$, Notifications, StoridgeProfileService, StoridgeManager, ModalService) {
|
||||
.controller('StoridgeProfileController', ['$scope', '$state', '$transition$', 'Notifications', 'StoridgeProfileService', 'ModalService',
|
||||
function ($scope, $state, $transition$, Notifications, StoridgeProfileService, ModalService) {
|
||||
|
||||
$scope.state = {
|
||||
NoLimit: false,
|
||||
|
@ -88,11 +88,6 @@ function ($scope, $state, $transition$, Notifications, StoridgeProfileService, S
|
|||
});
|
||||
}
|
||||
|
||||
StoridgeManager.init()
|
||||
.then(function success() {
|
||||
initView();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to communicate with Storidge API');
|
||||
});
|
||||
initView();
|
||||
|
||||
}]);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
angular.module('extension.storidge')
|
||||
.controller('StoridgeProfilesController', ['$q', '$scope', '$state', 'Notifications', 'StoridgeProfileService', 'StoridgeManager',
|
||||
function ($q, $scope, $state, Notifications, StoridgeProfileService, StoridgeManager) {
|
||||
.controller('StoridgeProfilesController', ['$q', '$scope', '$state', 'Notifications', 'StoridgeProfileService',
|
||||
function ($q, $scope, $state, Notifications, StoridgeProfileService) {
|
||||
|
||||
$scope.state = {
|
||||
actionInProgress: false
|
||||
|
@ -60,11 +60,5 @@ function ($q, $scope, $state, Notifications, StoridgeProfileService, StoridgeMan
|
|||
});
|
||||
}
|
||||
|
||||
StoridgeManager.init()
|
||||
.then(function success() {
|
||||
initView();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to communicate with Storidge API');
|
||||
});
|
||||
initView();
|
||||
}]);
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
angular.module('portainer.app')
|
||||
.factory('Extensions', ['$resource', 'EndpointProvider', 'API_ENDPOINT_ENDPOINTS', function Extensions($resource, EndpointProvider, API_ENDPOINT_ENDPOINTS) {
|
||||
'use strict';
|
||||
return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/extensions', {
|
||||
endpointId: EndpointProvider.endpointID
|
||||
},
|
||||
{
|
||||
register: { method: 'POST', params: { endpointId: '@endpointId' } }
|
||||
});
|
||||
}]);
|
|
@ -66,6 +66,7 @@ angular.module('portainer.app')
|
|||
TLSSkipVerify: TLSSkipVerify,
|
||||
TLSSkipClientVerify: TLSSkipClientVerify
|
||||
};
|
||||
|
||||
var deferred = $q.defer();
|
||||
Endpoints.create({}, endpoint).$promise
|
||||
.then(function success(data) {
|
||||
|
@ -85,6 +86,7 @@ angular.module('portainer.app')
|
|||
deferred.notify({upload: false});
|
||||
deferred.reject({msg: 'Unable to upload TLS certs', err: err});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
angular.module('portainer.app')
|
||||
.factory('ExtensionService', ['Extensions', function ExtensionServiceFactory(Extensions) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.registerStoridgeExtension = function(endpointId, url) {
|
||||
var payload = {
|
||||
endpointId: endpointId,
|
||||
Type: 1,
|
||||
URL: url
|
||||
};
|
||||
|
||||
return Extensions.register(payload).$promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
|
@ -1,35 +1,71 @@
|
|||
angular.module('portainer.app')
|
||||
.factory('ExtensionManager', ['$q', 'PluginService', 'StoridgeManager', function ExtensionManagerFactory($q, PluginService, StoridgeManager) {
|
||||
.factory('ExtensionManager', ['$q', 'PluginService', 'SystemService', 'ExtensionService',
|
||||
function ExtensionManagerFactory($q, PluginService, SystemService, ExtensionService) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.init = function() {
|
||||
return $q.all(
|
||||
StoridgeManager.init()
|
||||
);
|
||||
};
|
||||
|
||||
service.reset = function() {
|
||||
StoridgeManager.reset();
|
||||
};
|
||||
|
||||
service.extensions = function() {
|
||||
service.initEndpointExtensions = function(endpointId) {
|
||||
var deferred = $q.defer();
|
||||
var extensions = [];
|
||||
|
||||
PluginService.volumePlugins()
|
||||
SystemService.version()
|
||||
.then(function success(data) {
|
||||
var volumePlugins = data;
|
||||
if (_.includes(volumePlugins, 'cio:latest')) {
|
||||
extensions.push('storidge');
|
||||
}
|
||||
var endpointAPIVersion = parseFloat(data.ApiVersion);
|
||||
|
||||
return $q.all([
|
||||
endpointAPIVersion >= 1.25 ? initStoridgeExtension(endpointId): null
|
||||
]);
|
||||
})
|
||||
.finally(function final() {
|
||||
.then(function success(data) {
|
||||
var extensions = data.filter(function filterNull(x) {
|
||||
return x;
|
||||
});
|
||||
deferred.resolve(extensions);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to connect to the Docker environment', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
function initStoridgeExtension(endpointId) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
PluginService.volumePlugins()
|
||||
.then(function success(data) {
|
||||
var volumePlugins = data;
|
||||
if (_.includes(volumePlugins, 'cio:latest')) {
|
||||
return registerStoridgeUsingSwarmManagerIP(endpointId);
|
||||
}
|
||||
})
|
||||
.then(function success(data) {
|
||||
deferred.resolve(data);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'An error occured during Storidge extension check', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function registerStoridgeUsingSwarmManagerIP(endpointId) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
SystemService.info()
|
||||
.then(function success(data) {
|
||||
var managerIP = data.Swarm.NodeAddr;
|
||||
var storidgeAPIURL = 'tcp://' + managerIP + ':8282';
|
||||
return ExtensionService.registerStoridgeExtension(endpointId, storidgeAPIURL);
|
||||
})
|
||||
.then(function success(data) {
|
||||
deferred.resolve(data);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'An error occured during Storidge extension initialization', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
return service;
|
||||
}]);
|
||||
|
|
|
@ -41,15 +41,6 @@ angular.module('portainer.app')
|
|||
getPaginationLimit: function(key) {
|
||||
return localStorageService.cookie.get('pagination_' + key);
|
||||
},
|
||||
storeStoridgeAPIURL: function(url) {
|
||||
localStorageService.set('STORIDGE_API_URL', url);
|
||||
},
|
||||
getStoridgeAPIURL: function() {
|
||||
return localStorageService.get('STORIDGE_API_URL');
|
||||
},
|
||||
clearStoridgeAPIURL: function() {
|
||||
return localStorageService.remove('STORIDGE_API_URL');
|
||||
},
|
||||
getDataTableOrder: function(key) {
|
||||
return localStorageService.get('datatable_order_' + key);
|
||||
},
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
angular.module('portainer.app')
|
||||
.factory('StateManager', ['$q', 'SystemService', 'InfoHelper', 'LocalStorage', 'SettingsService', 'StatusService', 'ExtensionManager', 'APPLICATION_CACHE_VALIDITY', function StateManagerFactory($q, SystemService, InfoHelper, LocalStorage, SettingsService, StatusService, ExtensionManager, APPLICATION_CACHE_VALIDITY) {
|
||||
.factory('StateManager', ['$q', 'SystemService', 'InfoHelper', 'LocalStorage', 'SettingsService', 'StatusService', 'APPLICATION_CACHE_VALIDITY',
|
||||
function StateManagerFactory($q, SystemService, InfoHelper, LocalStorage, SettingsService, StatusService, APPLICATION_CACHE_VALIDITY) {
|
||||
'use strict';
|
||||
|
||||
var manager = {};
|
||||
|
@ -107,8 +108,24 @@ angular.module('portainer.app')
|
|||
return deferred.promise;
|
||||
};
|
||||
|
||||
manager.updateEndpointState = function(loading) {
|
||||
|
||||
function assignExtensions(endpointExtensions) {
|
||||
console.log(JSON.stringify(endpointExtensions, null, 4));
|
||||
var extensions = [];
|
||||
|
||||
for (var i = 0; i < endpointExtensions.length; i++) {
|
||||
var extension = endpointExtensions[i];
|
||||
if (extension.Type === 1) {
|
||||
extensions.push('storidge');
|
||||
}
|
||||
}
|
||||
|
||||
return extensions;
|
||||
}
|
||||
|
||||
manager.updateEndpointState = function(loading, extensions) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
if (loading) {
|
||||
state.loading = true;
|
||||
}
|
||||
|
@ -121,10 +138,7 @@ angular.module('portainer.app')
|
|||
var endpointAPIVersion = parseFloat(data.version.ApiVersion);
|
||||
state.endpoint.mode = endpointMode;
|
||||
state.endpoint.apiVersion = endpointAPIVersion;
|
||||
return $q.when(endpointAPIVersion < 1.25 || ExtensionManager.extensions());
|
||||
})
|
||||
.then(function success(data) {
|
||||
state.endpoint.extensions = data instanceof Array ? data : [];
|
||||
state.endpoint.extensions = assignExtensions(extensions);
|
||||
LocalStorage.storeEndpointState(state.endpoint);
|
||||
deferred.resolve();
|
||||
})
|
||||
|
|
|
@ -18,7 +18,7 @@ function ($scope, $state, $transition$, $window, $timeout, $sanitize, Authentica
|
|||
if (!endpointID) {
|
||||
EndpointProvider.setEndpointID(endpoint.Id);
|
||||
}
|
||||
StateManager.updateEndpointState(true)
|
||||
StateManager.updateEndpointState(true, endpoint.Extensions)
|
||||
.then(function success(data) {
|
||||
$state.go('docker.dashboard');
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
angular.module('portainer.app')
|
||||
.controller('EndpointsController', ['$scope', '$state', '$filter', 'EndpointService', 'Notifications',
|
||||
function ($scope, $state, $filter, EndpointService, Notifications) {
|
||||
.controller('EndpointsController', ['$scope', '$state', '$filter', 'EndpointService', 'Notifications', 'ExtensionManager', 'EndpointProvider',
|
||||
function ($scope, $state, $filter, EndpointService, Notifications, ExtensionManager, EndpointProvider) {
|
||||
$scope.state = {
|
||||
uploadInProgress: false,
|
||||
actionInProgress: false
|
||||
|
@ -31,9 +31,22 @@ function ($scope, $state, $filter, EndpointService, Notifications) {
|
|||
var TLSKeyFile = TLSSkipClientVerify ? null : securityData.TLSKey;
|
||||
|
||||
$scope.state.actionInProgress = true;
|
||||
EndpointService.createRemoteEndpoint(name, URL, PublicURL, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile).then(function success(data) {
|
||||
Notifications.success('Endpoint created', name);
|
||||
$state.reload();
|
||||
EndpointService.createRemoteEndpoint(name, URL, PublicURL, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile)
|
||||
.then(function success(data) {
|
||||
var currentEndpointId = EndpointProvider.endpointID();
|
||||
EndpointProvider.setEndpointID(data.Id);
|
||||
ExtensionManager.initEndpointExtensions(data.Id)
|
||||
.then(function success(data) {
|
||||
Notifications.success('Endpoint created', name);
|
||||
$state.reload();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to create endpoint');
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.actionInProgress = false;
|
||||
EndpointProvider.setEndpointID(currentEndpointId);
|
||||
});
|
||||
}, function error(err) {
|
||||
$scope.state.uploadInProgress = false;
|
||||
$scope.state.actionInProgress = false;
|
||||
|
|
|
@ -30,9 +30,9 @@ function ($scope, $state, $sanitize, Notifications, Authentication, StateManager
|
|||
if (data.length === 0) {
|
||||
$state.go('portainer.init.endpoint');
|
||||
} else {
|
||||
var endpointID = data[0].Id;
|
||||
EndpointProvider.setEndpointID(endpointID);
|
||||
StateManager.updateEndpointState(false)
|
||||
var endpoint = data[0];
|
||||
EndpointProvider.setEndpointID(endpoint.Id);
|
||||
StateManager.updateEndpointState(false, endpoint.Extensions)
|
||||
.then(function success() {
|
||||
$state.go('docker.dashboard');
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
angular.module('portainer.app')
|
||||
.controller('InitEndpointController', ['$scope', '$state', 'EndpointService', 'StateManager', 'EndpointProvider', 'Notifications',
|
||||
function ($scope, $state, EndpointService, StateManager, EndpointProvider, Notifications) {
|
||||
.controller('InitEndpointController', ['$scope', '$state', 'EndpointService', 'StateManager', 'EndpointProvider', 'Notifications', 'ExtensionManager',
|
||||
function ($scope, $state, EndpointService, StateManager, EndpointProvider, Notifications, ExtensionManager) {
|
||||
|
||||
if (!_.isEmpty($scope.applicationState.endpoint)) {
|
||||
$state.go('docker.dashboard');
|
||||
|
@ -35,7 +35,11 @@ function ($scope, $state, EndpointService, StateManager, EndpointProvider, Notif
|
|||
.then(function success(data) {
|
||||
endpointID = data.Id;
|
||||
EndpointProvider.setEndpointID(endpointID);
|
||||
return StateManager.updateEndpointState(false);
|
||||
return ExtensionManager.initEndpointExtensions(endpointID);
|
||||
})
|
||||
.then(function success(data) {
|
||||
var extensions = data;
|
||||
return StateManager.updateEndpointState(false, extensions);
|
||||
})
|
||||
.then(function success(data) {
|
||||
$state.go('docker.dashboard');
|
||||
|
@ -66,7 +70,11 @@ function ($scope, $state, EndpointService, StateManager, EndpointProvider, Notif
|
|||
.then(function success(data) {
|
||||
endpointID = data.Id;
|
||||
EndpointProvider.setEndpointID(endpointID);
|
||||
return StateManager.updateEndpointState(false);
|
||||
return ExtensionManager.initEndpointExtensions(endpointID);
|
||||
})
|
||||
.then(function success(data) {
|
||||
var extensions = data;
|
||||
return StateManager.updateEndpointState(false, extensions);
|
||||
})
|
||||
.then(function success(data) {
|
||||
$state.go('docker.dashboard');
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
sidebar-toggled-on="toggle"
|
||||
current-state="$state.current.name"
|
||||
></docker-sidebar-content>
|
||||
<li class="sidebar-title" ng-if="applicationState.endpoint.extensions.length > 0 && isAdmin && applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && applicationState.endpoint.mode.role === 'MANAGER'">
|
||||
<li class="sidebar-title" ng-if="applicationState.endpoint.extensions.length > 0">
|
||||
<span>Extensions</span>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="applicationState.endpoint.extensions.indexOf('storidge') !== -1 && applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && applicationState.endpoint.mode.role === 'MANAGER'">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
angular.module('portainer.app')
|
||||
.controller('SidebarController', ['$q', '$scope', '$state', 'Settings', 'EndpointService', 'StateManager', 'EndpointProvider', 'Notifications', 'Authentication', 'UserService', 'ExtensionManager',
|
||||
function ($q, $scope, $state, Settings, EndpointService, StateManager, EndpointProvider, Notifications, Authentication, UserService, ExtensionManager) {
|
||||
.controller('SidebarController', ['$q', '$scope', '$state', 'Settings', 'EndpointService', 'StateManager', 'EndpointProvider', 'Notifications', 'Authentication', 'UserService',
|
||||
function ($q, $scope, $state, Settings, EndpointService, StateManager, EndpointProvider, Notifications, Authentication, UserService) {
|
||||
|
||||
$scope.switchEndpoint = function(endpoint) {
|
||||
var activeEndpointID = EndpointProvider.endpointID();
|
||||
|
@ -8,9 +8,8 @@ function ($q, $scope, $state, Settings, EndpointService, StateManager, EndpointP
|
|||
EndpointProvider.setEndpointID(endpoint.Id);
|
||||
EndpointProvider.setEndpointPublicURL(endpoint.PublicURL);
|
||||
|
||||
StateManager.updateEndpointState(true)
|
||||
StateManager.updateEndpointState(true, endpoint.Extensions)
|
||||
.then(function success() {
|
||||
ExtensionManager.reset();
|
||||
$state.go('docker.dashboard');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
|
@ -47,7 +46,7 @@ function ($q, $scope, $state, Settings, EndpointService, StateManager, EndpointP
|
|||
$scope.displayExternalContributors = StateManager.getState().application.displayExternalContributors;
|
||||
$scope.logo = StateManager.getState().application.logo;
|
||||
$scope.endpoints = [];
|
||||
|
||||
|
||||
EndpointService.endpoints()
|
||||
.then(function success(data) {
|
||||
var endpoints = data;
|
||||
|
|
Loading…
Reference in New Issue