mirror of https://github.com/portainer/portainer
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 codeclimatepull/1581/head
parent
e1bf9599ef
commit
edadce359c
|
@ -54,10 +54,15 @@ func (manager *StackManager) Logout(endpoint *portainer.Endpoint) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deploy executes the docker stack deploy command.
|
// 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)
|
stackFilePath := path.Join(stack.ProjectPath, stack.EntryPoint)
|
||||||
command, args := prepareDockerCommandAndArgs(manager.binaryPath, endpoint)
|
command, args := prepareDockerCommandAndArgs(manager.binaryPath, endpoint)
|
||||||
args = append(args, "stack", "deploy", "--with-registry-auth", "--compose-file", stackFilePath, stack.Name)
|
|
||||||
|
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)
|
env := make([]string, 0)
|
||||||
for _, envvar := range stack.Env {
|
for _, envvar := range stack.Env {
|
||||||
|
|
|
@ -37,6 +37,14 @@ type StackHandler struct {
|
||||||
StackManager portainer.StackManager
|
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.
|
// NewStackHandler returns a new instance of StackHandler.
|
||||||
func NewStackHandler(bouncer *security.RequestBouncer) *StackHandler {
|
func NewStackHandler(bouncer *security.RequestBouncer) *StackHandler {
|
||||||
h := &StackHandler{
|
h := &StackHandler{
|
||||||
|
@ -78,6 +86,7 @@ type (
|
||||||
putStackRequest struct {
|
putStackRequest struct {
|
||||||
StackFileContent string `valid:"required"`
|
StackFileContent string `valid:"required"`
|
||||||
Env []portainer.Pair `valid:""`
|
Env []portainer.Pair `valid:""`
|
||||||
|
Prune bool `valid:"-"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -207,7 +216,14 @@ func (handler *StackHandler) handlePostStacksStringMethod(w http.ResponseWriter,
|
||||||
return
|
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 {
|
if err != nil {
|
||||||
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
||||||
return
|
return
|
||||||
|
@ -334,7 +350,14 @@ func (handler *StackHandler) handlePostStacksRepositoryMethod(w http.ResponseWri
|
||||||
return
|
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 {
|
if err != nil {
|
||||||
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
||||||
return
|
return
|
||||||
|
@ -445,7 +468,14 @@ func (handler *StackHandler) handlePostStacksFileMethod(w http.ResponseWriter, r
|
||||||
return
|
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 {
|
if err != nil {
|
||||||
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
||||||
return
|
return
|
||||||
|
@ -637,7 +667,14 @@ func (handler *StackHandler) handlePutStack(w http.ResponseWriter, r *http.Reque
|
||||||
return
|
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 {
|
if err != nil {
|
||||||
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
||||||
return
|
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()
|
handler.stackCreationMutex.Lock()
|
||||||
|
|
||||||
err := handler.StackManager.Login(dockerhub, registries, endpoint)
|
err := handler.StackManager.Login(config.dockerhub, config.registries, config.endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handler.stackCreationMutex.Unlock()
|
handler.stackCreationMutex.Unlock()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = handler.StackManager.Deploy(stack, endpoint)
|
err = handler.StackManager.Deploy(config.stack, config.prune, config.endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handler.stackCreationMutex.Unlock()
|
handler.stackCreationMutex.Unlock()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = handler.StackManager.Logout(endpoint)
|
err = handler.StackManager.Logout(config.endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handler.stackCreationMutex.Unlock()
|
handler.stackCreationMutex.Unlock()
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -384,7 +384,7 @@ type (
|
||||||
StackManager interface {
|
StackManager interface {
|
||||||
Login(dockerhub *DockerHub, registries []Registry, endpoint *Endpoint) error
|
Login(dockerhub *DockerHub, registries []Registry, endpoint *Endpoint) error
|
||||||
Logout(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
|
Remove(stack *Stack, endpoint *Endpoint) error
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -2976,10 +2976,14 @@ definitions:
|
||||||
description: "A list of environment variables used during stack deployment"
|
description: "A list of environment variables used during stack deployment"
|
||||||
items:
|
items:
|
||||||
$ref: "#/definitions/Stack_Env"
|
$ref: "#/definitions/Stack_Env"
|
||||||
|
Prune:
|
||||||
|
type: "boolean"
|
||||||
|
example: false
|
||||||
|
description: "Prune services that are no longer referenced"
|
||||||
StackFileInspectResponse:
|
StackFileInspectResponse:
|
||||||
type: "object"
|
type: "object"
|
||||||
properties:
|
properties:
|
||||||
StackFileContent:
|
StackFileContent:
|
||||||
type: "string"
|
type: "string"
|
||||||
example: "version: 3\n services:\n web:\n image:nginx"
|
example: "version: 3\n services:\n web:\n image:nginx"
|
||||||
description: "Content of the Stack file."
|
description: "Content of the Stack file."
|
||||||
|
|
|
@ -90,6 +90,22 @@
|
||||||
<!-- !environment-variable-input-list -->
|
<!-- !environment-variable-input-list -->
|
||||||
</div>
|
</div>
|
||||||
<!-- !environment-variables -->
|
<!-- !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">
|
<div class="col-sm-12 form-section-title">
|
||||||
Actions
|
Actions
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,14 +7,19 @@ function ($q, $scope, $state, $transition$, $document, StackService, NodeService
|
||||||
publicURL: EndpointProvider.endpointPublicURL()
|
publicURL: EndpointProvider.endpointPublicURL()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.formValues = {
|
||||||
|
Prune: false
|
||||||
|
};
|
||||||
|
|
||||||
$scope.deployStack = function () {
|
$scope.deployStack = function () {
|
||||||
// The codemirror editor does not work with ng-model so we need to retrieve
|
// The codemirror editor does not work with ng-model so we need to retrieve
|
||||||
// the value directly from the editor.
|
// the value directly from the editor.
|
||||||
var stackFile = $scope.editor.getValue();
|
var stackFile = $scope.editor.getValue();
|
||||||
var env = FormHelper.removeInvalidEnvVars($scope.stack.Env);
|
var env = FormHelper.removeInvalidEnvVars($scope.stack.Env);
|
||||||
|
var prune = $scope.formValues.Prune;
|
||||||
|
|
||||||
$scope.state.actionInProgress = true;
|
$scope.state.actionInProgress = true;
|
||||||
StackService.updateStack($scope.stack.Id, stackFile, env)
|
StackService.updateStack($scope.stack.Id, stackFile, env, prune)
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
Notifications.success('Stack successfully deployed');
|
Notifications.success('Stack successfully deployed');
|
||||||
$state.reload();
|
$state.reload();
|
||||||
|
@ -37,6 +42,7 @@ function ($q, $scope, $state, $transition$, $document, StackService, NodeService
|
||||||
|
|
||||||
function initView() {
|
function initView() {
|
||||||
var stackId = $transition$.params().id;
|
var stackId = $transition$.params().id;
|
||||||
|
var apiVersion = $scope.applicationState.endpoint.apiVersion;
|
||||||
|
|
||||||
StackService.stack(stackId)
|
StackService.stack(stackId)
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
|
|
|
@ -167,8 +167,8 @@ function StackServiceFactory($q, Stack, ResourceControlService, FileUploadServic
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
service.updateStack = function(id, stackFile, env) {
|
service.updateStack = function(id, stackFile, env, prune) {
|
||||||
return Stack.update({ id: id, StackFileContent: stackFile, Env: env }).$promise;
|
return Stack.update({ id: id, StackFileContent: stackFile, Env: env, Prune: prune}).$promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
return service;
|
return service;
|
||||||
|
|
Loading…
Reference in New Issue