diff --git a/api/bolt/migrate_dbversion11.go b/api/bolt/migrate_dbversion11.go index a16cc093b..dcd0e1100 100644 --- a/api/bolt/migrate_dbversion11.go +++ b/api/bolt/migrate_dbversion11.go @@ -1,5 +1,7 @@ package bolt +import "github.com/portainer/portainer" + func (m *Migrator) updateEndpointsToVersion12() error { legacyEndpoints, err := m.EndpointService.Endpoints() if err != nil { @@ -35,3 +37,21 @@ func (m *Migrator) updateEndpointGroupsToVersion12() error { return nil } + +func (m *Migrator) updateStacksToVersion12() error { + legacyStacks, err := m.StackService.Stacks() + if err != nil { + return err + } + + for _, stack := range legacyStacks { + stack.Type = portainer.DockerSwarmStack + + err = m.StackService.UpdateStack(stack.ID, &stack) + if err != nil { + return err + } + } + + return nil +} diff --git a/api/bolt/migrator.go b/api/bolt/migrator.go index 8f914a7b4..ef3901d42 100644 --- a/api/bolt/migrator.go +++ b/api/bolt/migrator.go @@ -4,27 +4,30 @@ import "github.com/portainer/portainer" // Migrator defines a service to migrate data after a Portainer version update. type Migrator struct { - UserService *UserService - EndpointService *EndpointService + CurrentDBVersion int + store *Store + EndpointGroupService *EndpointGroupService + EndpointService *EndpointService ResourceControlService *ResourceControlService SettingsService *SettingsService + StackService *StackService + UserService *UserService VersionService *VersionService - CurrentDBVersion int - store *Store } // NewMigrator creates a new Migrator. func NewMigrator(store *Store, version int) *Migrator { return &Migrator{ - UserService: store.UserService, - EndpointService: store.EndpointService, - EndpointGroupService: store.EndpointGroupService, - ResourceControlService: store.ResourceControlService, - SettingsService: store.SettingsService, - VersionService: store.VersionService, CurrentDBVersion: version, store: store, + EndpointGroupService: store.EndpointGroupService, + EndpointService: store.EndpointService, + ResourceControlService: store.ResourceControlService, + SettingsService: store.SettingsService, + StackService: store.StackService, + UserService: store.UserService, + VersionService: store.VersionService, } } @@ -122,6 +125,7 @@ func (m *Migrator) Migrate() error { } } + // Portainer 1.17.1-dev if m.CurrentDBVersion < 12 { err := m.updateEndpointsToVersion12() if err != nil { @@ -132,6 +136,11 @@ func (m *Migrator) Migrate() error { if err != nil { return err } + + err = m.updateStacksToVersion12() + if err != nil { + return err + } } err := m.VersionService.StoreDBVersion(portainer.DBVersion) diff --git a/api/http/handler/stacks/stack_delete.go b/api/http/handler/stacks/stack_delete.go index 4deed5612..5e1bcceaf 100644 --- a/api/http/handler/stacks/stack_delete.go +++ b/api/http/handler/stacks/stack_delete.go @@ -46,7 +46,20 @@ func (handler *Handler) stackDelete(w http.ResponseWriter, r *http.Request) *htt } } - endpoint, err := handler.EndpointService.Endpoint(stack.EndpointID) + // TODO: this is a work-around for stacks created with Portainer version >= 1.17.1 + // The EndpointID property is not available for these stacks, this API endpoint + // can use the optional EndpointID query parameter to set a valid endpoint identifier to be + // used in the context of this request. + endpointID, err := request.RetrieveNumericQueryParameter(r, "endpointId", true) + if err != nil { + return &httperror.HandlerError{http.StatusBadRequest, "Invalid query parameter: endpointId", err} + } + endpointIdentifier := stack.EndpointID + if endpointID != 0 { + endpointIdentifier = portainer.EndpointID(endpointID) + } + + endpoint, err := handler.EndpointService.Endpoint(endpointIdentifier) if err == portainer.ErrEndpointNotFound { return &httperror.HandlerError{http.StatusNotFound, "Unable to find the endpoint associated to the stack inside the database", err} } else if err != nil { diff --git a/api/http/handler/stacks/stack_update.go b/api/http/handler/stacks/stack_update.go index 98c25d60f..9867c3887 100644 --- a/api/http/handler/stacks/stack_update.go +++ b/api/http/handler/stacks/stack_update.go @@ -36,7 +36,7 @@ func (payload *updateSwarmStackPayload) Validate(r *http.Request) error { return nil } -// PUT request on /api/stacks/:id +// PUT request on /api/stacks/:id?endpointId= func (handler *Handler) stackUpdate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { stackID, err := request.RetrieveRouteVariableValue(r, "id") if err != nil { @@ -66,6 +66,17 @@ func (handler *Handler) stackUpdate(w http.ResponseWriter, r *http.Request) *htt } } + // TODO: this is a work-around for stacks created with Portainer version >= 1.17.1 + // The EndpointID property is not available for these stacks, this API endpoint + // can use the optional EndpointID query parameter to associate a valid endpoint identifier to the stack. + endpointID, err := request.RetrieveNumericQueryParameter(r, "endpointId", true) + if err != nil { + return &httperror.HandlerError{http.StatusBadRequest, "Invalid query parameter: endpointId", err} + } + if endpointID != int(stack.EndpointID) { + stack.EndpointID = portainer.EndpointID(endpointID) + } + endpoint, err := handler.EndpointService.Endpoint(stack.EndpointID) if err == portainer.ErrEndpointNotFound { return &httperror.HandlerError{http.StatusNotFound, "Unable to find the endpoint associated to the stack inside the database", err} diff --git a/app/portainer/models/stack.js b/app/portainer/models/stack.js index 78571d30d..06666b588 100644 --- a/app/portainer/models/stack.js +++ b/app/portainer/models/stack.js @@ -3,6 +3,7 @@ function StackViewModel(data) { this.Type = data.Type; this.Name = data.Name; this.Checked = false; + this.EndpointId = data.EndpointId; this.Env = data.Env ? data.Env : []; if (data.ResourceControl && data.ResourceControl.Id !== 0) { this.ResourceControl = new ResourceControlViewModel(data.ResourceControl); diff --git a/app/portainer/services/api/stackService.js b/app/portainer/services/api/stackService.js index 358b55d4b..66f2fd6bb 100644 --- a/app/portainer/services/api/stackService.js +++ b/app/portainer/services/api/stackService.js @@ -174,8 +174,8 @@ function StackServiceFactory($q, Stack, ResourceControlService, FileUploadServic return deferred.promise; }; - service.updateStack = function(id, stackFile, env, prune) { - return Stack.update({ id: id, StackFileContent: stackFile, Env: env, Prune: prune}).$promise; + service.updateStack = function(stack, stackFile, env, prune) { + return Stack.update({ endpointId: stack.EndpointId }, { id: stack.Id, StackFileContent: stackFile, Env: env, Prune: prune }).$promise; }; service.createComposeStackFromFileUpload = function(name, stackFile, endpointId) { diff --git a/app/portainer/views/stacks/edit/stackController.js b/app/portainer/views/stacks/edit/stackController.js index 8985edbef..60f6c19be 100644 --- a/app/portainer/views/stacks/edit/stackController.js +++ b/app/portainer/views/stacks/edit/stackController.js @@ -1,6 +1,6 @@ angular.module('portainer.app') -.controller('StackController', ['$q', '$scope', '$state', '$transition$', 'StackService', 'NodeService', 'ServiceService', 'TaskService', 'ContainerService', 'ServiceHelper', 'TaskHelper', 'Notifications', 'FormHelper', -function ($q, $scope, $state, $transition$, StackService, NodeService, ServiceService, TaskService, ContainerService, ServiceHelper, TaskHelper, Notifications, FormHelper) { +.controller('StackController', ['$q', '$scope', '$state', '$transition$', 'StackService', 'NodeService', 'ServiceService', 'TaskService', 'ContainerService', 'ServiceHelper', 'TaskHelper', 'Notifications', 'FormHelper', 'EndpointProvider', +function ($q, $scope, $state, $transition$, StackService, NodeService, ServiceService, TaskService, ContainerService, ServiceHelper, TaskHelper, Notifications, FormHelper, EndpointProvider) { $scope.state = { actionInProgress: false, @@ -15,9 +15,19 @@ function ($q, $scope, $state, $transition$, StackService, NodeService, ServiceSe var stackFile = $scope.stackFileContent; var env = FormHelper.removeInvalidEnvVars($scope.stack.Env); var prune = $scope.formValues.Prune; + var stack = $scope.stack; + + // TODO: this is a work-around for stacks created with Portainer version >= 1.17.1 + // The EndpointID property is not available for these stacks, we can pass + // the current endpoint identifier as a part of the update request. It will be used if + // the EndpointID property is not defined on the stack. + var endpointId = EndpointProvider.endpointID(); + if (stack.EndpointId === 0) { + stack.EndpointId = endpointId; + } $scope.state.actionInProgress = true; - StackService.updateStack($scope.stack.Id, stackFile, env, prune) + StackService.updateStack(stack, stackFile, env, prune) .then(function success(data) { Notifications.success('Stack successfully deployed'); $state.reload();