feat(stack-details): add stack deploy prune option (#1567)

* feat(stack-details): add stack deploy prune option

* fix go fmt issues

* add changes proposed by reviewer

* refactor deployStack as suggested by codeclimate
pull/1581/head
Miguel A. C 2018-01-20 18:05:01 +01:00 committed by Anthony Lapenna
parent e1bf9599ef
commit edadce359c
7 changed files with 83 additions and 15 deletions

View File

@ -54,10 +54,15 @@ func (manager *StackManager) Logout(endpoint *portainer.Endpoint) error {
}
// Deploy executes the docker stack deploy command.
func (manager *StackManager) Deploy(stack *portainer.Stack, endpoint *portainer.Endpoint) error {
func (manager *StackManager) Deploy(stack *portainer.Stack, prune bool, endpoint *portainer.Endpoint) error {
stackFilePath := path.Join(stack.ProjectPath, stack.EntryPoint)
command, args := prepareDockerCommandAndArgs(manager.binaryPath, endpoint)
if prune {
args = append(args, "stack", "deploy", "--prune", "--with-registry-auth", "--compose-file", stackFilePath, stack.Name)
} else {
args = append(args, "stack", "deploy", "--with-registry-auth", "--compose-file", stackFilePath, stack.Name)
}
env := make([]string, 0)
for _, envvar := range stack.Env {

View File

@ -37,6 +37,14 @@ type StackHandler struct {
StackManager portainer.StackManager
}
type stackDeploymentConfig struct {
endpoint *portainer.Endpoint
stack *portainer.Stack
prune bool
dockerhub *portainer.DockerHub
registries []portainer.Registry
}
// NewStackHandler returns a new instance of StackHandler.
func NewStackHandler(bouncer *security.RequestBouncer) *StackHandler {
h := &StackHandler{
@ -78,6 +86,7 @@ type (
putStackRequest struct {
StackFileContent string `valid:"required"`
Env []portainer.Pair `valid:""`
Prune bool `valid:"-"`
}
)
@ -207,7 +216,14 @@ func (handler *StackHandler) handlePostStacksStringMethod(w http.ResponseWriter,
return
}
err = handler.deployStack(endpoint, stack, dockerhub, filteredRegistries)
config := stackDeploymentConfig{
stack: stack,
endpoint: endpoint,
dockerhub: dockerhub,
registries: filteredRegistries,
prune: false,
}
err = handler.deployStack(&config)
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
@ -334,7 +350,14 @@ func (handler *StackHandler) handlePostStacksRepositoryMethod(w http.ResponseWri
return
}
err = handler.deployStack(endpoint, stack, dockerhub, filteredRegistries)
config := stackDeploymentConfig{
stack: stack,
endpoint: endpoint,
dockerhub: dockerhub,
registries: filteredRegistries,
prune: false,
}
err = handler.deployStack(&config)
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
@ -445,7 +468,14 @@ func (handler *StackHandler) handlePostStacksFileMethod(w http.ResponseWriter, r
return
}
err = handler.deployStack(endpoint, stack, dockerhub, filteredRegistries)
config := stackDeploymentConfig{
stack: stack,
endpoint: endpoint,
dockerhub: dockerhub,
registries: filteredRegistries,
prune: false,
}
err = handler.deployStack(&config)
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
@ -637,7 +667,14 @@ func (handler *StackHandler) handlePutStack(w http.ResponseWriter, r *http.Reque
return
}
err = handler.deployStack(endpoint, stack, dockerhub, filteredRegistries)
config := stackDeploymentConfig{
stack: stack,
endpoint: endpoint,
dockerhub: dockerhub,
registries: filteredRegistries,
prune: req.Prune,
}
err = handler.deployStack(&config)
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
@ -732,22 +769,22 @@ func (handler *StackHandler) handleDeleteStack(w http.ResponseWriter, r *http.Re
}
}
func (handler *StackHandler) deployStack(endpoint *portainer.Endpoint, stack *portainer.Stack, dockerhub *portainer.DockerHub, registries []portainer.Registry) error {
func (handler *StackHandler) deployStack(config *stackDeploymentConfig) error {
handler.stackCreationMutex.Lock()
err := handler.StackManager.Login(dockerhub, registries, endpoint)
err := handler.StackManager.Login(config.dockerhub, config.registries, config.endpoint)
if err != nil {
handler.stackCreationMutex.Unlock()
return err
}
err = handler.StackManager.Deploy(stack, endpoint)
err = handler.StackManager.Deploy(config.stack, config.prune, config.endpoint)
if err != nil {
handler.stackCreationMutex.Unlock()
return err
}
err = handler.StackManager.Logout(endpoint)
err = handler.StackManager.Logout(config.endpoint)
if err != nil {
handler.stackCreationMutex.Unlock()
return err

View File

@ -384,7 +384,7 @@ type (
StackManager interface {
Login(dockerhub *DockerHub, registries []Registry, endpoint *Endpoint) error
Logout(endpoint *Endpoint) error
Deploy(stack *Stack, endpoint *Endpoint) error
Deploy(stack *Stack, prune bool, endpoint *Endpoint) error
Remove(stack *Stack, endpoint *Endpoint) error
}
)

View File

@ -2976,6 +2976,10 @@ definitions:
description: "A list of environment variables used during stack deployment"
items:
$ref: "#/definitions/Stack_Env"
Prune:
type: "boolean"
example: false
description: "Prune services that are no longer referenced"
StackFileInspectResponse:
type: "object"
properties:

View File

@ -90,6 +90,22 @@
<!-- !environment-variable-input-list -->
</div>
<!-- !environment-variables -->
<!-- options -->
<div class="col-sm-12 form-section-title" ng-if="applicationState.endpoint.apiVersion >= 1.27">
Options
</div>
<div class="form-group" ng-if="applicationState.endpoint.apiVersion >= 1.27">
<div class="col-sm-12">
<label for="prune" class="control-label text-left">
Prune services
<portainer-tooltip position="bottom" message="Prune services that are no longer referenced."></portainer-tooltip>
</label>
<label class="switch" style="margin-left: 20px;">
<input name="prune" type="checkbox" ng-model="formValues.Prune"><i></i>
</label>
</div>
</div>
<!-- !options -->
<div class="col-sm-12 form-section-title">
Actions
</div>

View File

@ -7,14 +7,19 @@ function ($q, $scope, $state, $transition$, $document, StackService, NodeService
publicURL: EndpointProvider.endpointPublicURL()
};
$scope.formValues = {
Prune: false
};
$scope.deployStack = function () {
// The codemirror editor does not work with ng-model so we need to retrieve
// the value directly from the editor.
var stackFile = $scope.editor.getValue();
var env = FormHelper.removeInvalidEnvVars($scope.stack.Env);
var prune = $scope.formValues.Prune;
$scope.state.actionInProgress = true;
StackService.updateStack($scope.stack.Id, stackFile, env)
StackService.updateStack($scope.stack.Id, stackFile, env, prune)
.then(function success(data) {
Notifications.success('Stack successfully deployed');
$state.reload();
@ -37,6 +42,7 @@ function ($q, $scope, $state, $transition$, $document, StackService, NodeService
function initView() {
var stackId = $transition$.params().id;
var apiVersion = $scope.applicationState.endpoint.apiVersion;
StackService.stack(stackId)
.then(function success(data) {

View File

@ -167,8 +167,8 @@ function StackServiceFactory($q, Stack, ResourceControlService, FileUploadServic
return deferred.promise;
};
service.updateStack = function(id, stackFile, env) {
return Stack.update({ id: id, StackFileContent: stackFile, Env: env }).$promise;
service.updateStack = function(id, stackFile, env, prune) {
return Stack.update({ id: id, StackFileContent: stackFile, Env: env, Prune: prune}).$promise;
};
return service;