feat(stacks): allow to use images from private registries in stacks (#1327)

pull/1336/head
Anthony Lapenna 2017-10-26 14:22:09 +02:00 committed by GitHub
parent 34d40e4876
commit b5629c5b1a
5 changed files with 175 additions and 10 deletions

View File

@ -21,7 +21,38 @@ func NewStackManager(binaryPath string) *StackManager {
}
}
// Deploy will execute the Docker stack deploy command
// Login executes the docker login command against a list of registries (including DockerHub).
func (manager *StackManager) Login(dockerhub *portainer.DockerHub, registries []portainer.Registry, endpoint *portainer.Endpoint) error {
command, args := prepareDockerCommandAndArgs(manager.binaryPath, endpoint)
for _, registry := range registries {
if registry.Authentication {
registryArgs := append(args, "login", "--username", registry.Username, "--password", registry.Password, registry.URL)
err := runCommandAndCaptureStdErr(command, registryArgs)
if err != nil {
return err
}
}
}
if dockerhub.Authentication {
dockerhubArgs := append(args, "login", "--username", dockerhub.Username, "--password", dockerhub.Password)
err := runCommandAndCaptureStdErr(command, dockerhubArgs)
if err != nil {
return err
}
}
return nil
}
// Logout executes the docker logout command.
func (manager *StackManager) Logout(endpoint *portainer.Endpoint) error {
command, args := prepareDockerCommandAndArgs(manager.binaryPath, endpoint)
args = append(args, "logout")
return runCommandAndCaptureStdErr(command, args)
}
// Deploy executes the docker stack deploy command.
func (manager *StackManager) Deploy(stack *portainer.Stack, endpoint *portainer.Endpoint) error {
stackFilePath := path.Join(stack.ProjectPath, stack.EntryPoint)
command, args := prepareDockerCommandAndArgs(manager.binaryPath, endpoint)
@ -29,7 +60,7 @@ func (manager *StackManager) Deploy(stack *portainer.Stack, endpoint *portainer.
return runCommandAndCaptureStdErr(command, args)
}
// Remove will execute the Docker stack rm command
// Remove executes the docker stack rm command.
func (manager *StackManager) Remove(stack *portainer.Stack, endpoint *portainer.Endpoint) error {
command, args := prepareDockerCommandAndArgs(manager.binaryPath, endpoint)
args = append(args, "stack", "rm", stack.Name)

View File

@ -5,6 +5,7 @@ import (
"path"
"strconv"
"strings"
"sync"
"github.com/asaskevich/govalidator"
"github.com/portainer/portainer"
@ -22,6 +23,8 @@ import (
// StackHandler represents an HTTP API handler for managing Stack.
type StackHandler struct {
stackCreationMutex *sync.Mutex
stackDeletionMutex *sync.Mutex
*mux.Router
Logger *log.Logger
FileService portainer.FileService
@ -29,17 +32,21 @@ type StackHandler struct {
StackService portainer.StackService
EndpointService portainer.EndpointService
ResourceControlService portainer.ResourceControlService
RegistryService portainer.RegistryService
DockerHubService portainer.DockerHubService
StackManager portainer.StackManager
}
// NewStackHandler returns a new instance of StackHandler.
func NewStackHandler(bouncer *security.RequestBouncer) *StackHandler {
h := &StackHandler{
Router: mux.NewRouter(),
Logger: log.New(os.Stderr, "", log.LstdFlags),
Router: mux.NewRouter(),
stackCreationMutex: &sync.Mutex{},
stackDeletionMutex: &sync.Mutex{},
Logger: log.New(os.Stderr, "", log.LstdFlags),
}
h.Handle("/{endpointId}/stacks",
bouncer.AuthenticatedAccess(http.HandlerFunc(h.handlePostStacks))).Methods(http.MethodPost)
bouncer.RestrictedAccess(http.HandlerFunc(h.handlePostStacks))).Methods(http.MethodPost)
h.Handle("/{endpointId}/stacks",
bouncer.RestrictedAccess(http.HandlerFunc(h.handleGetStacks))).Methods(http.MethodGet)
h.Handle("/{endpointId}/stacks/{id}",
@ -173,7 +180,31 @@ func (handler *StackHandler) handlePostStacksStringMethod(w http.ResponseWriter,
return
}
err = handler.StackManager.Deploy(stack, endpoint)
securityContext, err := security.RetrieveRestrictedRequestContext(r)
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
}
dockerhub, err := handler.DockerHubService.DockerHub()
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
}
registries, err := handler.RegistryService.Registries()
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
}
filteredRegistries, err := security.FilterRegistries(registries, securityContext)
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
}
err = handler.deployStack(endpoint, stack, dockerhub, filteredRegistries)
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
@ -275,7 +306,31 @@ func (handler *StackHandler) handlePostStacksRepositoryMethod(w http.ResponseWri
return
}
err = handler.StackManager.Deploy(stack, endpoint)
securityContext, err := security.RetrieveRestrictedRequestContext(r)
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
}
dockerhub, err := handler.DockerHubService.DockerHub()
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
}
registries, err := handler.RegistryService.Registries()
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
}
filteredRegistries, err := security.FilterRegistries(registries, securityContext)
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
}
err = handler.deployStack(endpoint, stack, dockerhub, filteredRegistries)
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
@ -354,7 +409,31 @@ func (handler *StackHandler) handlePostStacksFileMethod(w http.ResponseWriter, r
return
}
err = handler.StackManager.Deploy(stack, endpoint)
securityContext, err := security.RetrieveRestrictedRequestContext(r)
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
}
dockerhub, err := handler.DockerHubService.DockerHub()
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
}
registries, err := handler.RegistryService.Registries()
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
}
filteredRegistries, err := security.FilterRegistries(registries, securityContext)
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
}
err = handler.deployStack(endpoint, stack, dockerhub, filteredRegistries)
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
@ -515,7 +594,31 @@ func (handler *StackHandler) handlePutStack(w http.ResponseWriter, r *http.Reque
return
}
err = handler.StackManager.Deploy(stack, endpoint)
securityContext, err := security.RetrieveRestrictedRequestContext(r)
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
}
dockerhub, err := handler.DockerHubService.DockerHub()
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
}
registries, err := handler.RegistryService.Registries()
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
}
filteredRegistries, err := security.FilterRegistries(registries, securityContext)
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
}
err = handler.deployStack(endpoint, stack, dockerhub, filteredRegistries)
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
@ -589,11 +692,13 @@ func (handler *StackHandler) handleDeleteStack(w http.ResponseWriter, r *http.Re
return
}
handler.stackDeletionMutex.Lock()
err = handler.StackManager.Remove(stack, endpoint)
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
}
handler.stackDeletionMutex.Unlock()
err = handler.StackService.DeleteStack(portainer.StackID(stackID))
if err != nil {
@ -607,3 +712,28 @@ func (handler *StackHandler) handleDeleteStack(w http.ResponseWriter, r *http.Re
return
}
}
func (handler *StackHandler) deployStack(endpoint *portainer.Endpoint, stack *portainer.Stack, dockerhub *portainer.DockerHub, registries []portainer.Registry) error {
handler.stackCreationMutex.Lock()
err := handler.StackManager.Login(dockerhub, registries, endpoint)
if err != nil {
handler.stackCreationMutex.Unlock()
return err
}
err = handler.StackManager.Deploy(stack, endpoint)
if err != nil {
handler.stackCreationMutex.Unlock()
return err
}
err = handler.StackManager.Logout(endpoint)
if err != nil {
handler.stackCreationMutex.Unlock()
return err
}
handler.stackCreationMutex.Unlock()
return nil
}

View File

@ -61,7 +61,7 @@ func FilterUsers(users []portainer.User, context *RestrictedRequestContext) []po
}
// FilterRegistries filters registries based on user role and team memberships.
// Non administrator users only have access to authorized endpoints.
// Non administrator users only have access to authorized registries.
func FilterRegistries(registries []portainer.Registry, context *RestrictedRequestContext) ([]portainer.Registry, error) {
filteredRegistries := registries

View File

@ -94,6 +94,8 @@ func (server *Server) Start() error {
stackHandler.ResourceControlService = server.ResourceControlService
stackHandler.StackManager = server.StackManager
stackHandler.GitService = server.GitService
stackHandler.RegistryService = server.RegistryService
stackHandler.DockerHubService = server.DockerHubService
server.Handler = &handler.Handler{
AuthHandler: authHandler,

View File

@ -379,6 +379,8 @@ type (
// StackManager represents a service to manage stacks.
StackManager interface {
Login(dockerhub *DockerHub, registries []Registry, endpoint *Endpoint) error
Logout(endpoint *Endpoint) error
Deploy(stack *Stack, endpoint *Endpoint) error
Remove(stack *Stack, endpoint *Endpoint) error
}