mirror of https://github.com/portainer/portainer
				
				
				
			refactor(container-creation): change order of container re-creation/duplication steps
* refactor(container-creation): change order of container creation steps * refactor(container-creation): remove nested methods * fix(container-creation): skip actions if old container missing * fix(container-creation): reject if user is not authorized * fix(container-creation): remove rejection on invalid form * refactor(container-creation): start container after duplicate * fix(container-creation): add form validation error message * fix(container-creation): pass correct id to create resource control * fix(container-creation): set action in progress after confirmationpull/2193/head
							parent
							
								
									7e08227ddb
								
							
						
					
					
						commit
						102e63e1e5
					
				| 
						 | 
					@ -2,6 +2,8 @@ angular.module('portainer.docker')
 | 
				
			||||||
.controller('CreateContainerController', ['$q', '$scope', '$state', '$timeout', '$transition$', '$filter', 'Container', 'ContainerHelper', 'Image', 'ImageHelper', 'Volume', 'NetworkService', 'ResourceControlService', 'Authentication', 'Notifications', 'ContainerService', 'ImageService', 'FormValidator', 'ModalService', 'RegistryService', 'SystemService', 'SettingsService', 'HttpRequestHelper',
 | 
					.controller('CreateContainerController', ['$q', '$scope', '$state', '$timeout', '$transition$', '$filter', 'Container', 'ContainerHelper', 'Image', 'ImageHelper', 'Volume', 'NetworkService', 'ResourceControlService', 'Authentication', 'Notifications', 'ContainerService', 'ImageService', 'FormValidator', 'ModalService', 'RegistryService', 'SystemService', 'SettingsService', 'HttpRequestHelper',
 | 
				
			||||||
function ($q, $scope, $state, $timeout, $transition$, $filter, Container, ContainerHelper, Image, ImageHelper, Volume, NetworkService, ResourceControlService, Authentication, Notifications, ContainerService, ImageService, FormValidator, ModalService, RegistryService, SystemService, SettingsService, HttpRequestHelper) {
 | 
					function ($q, $scope, $state, $timeout, $transition$, $filter, Container, ContainerHelper, Image, ImageHelper, Volume, NetworkService, ResourceControlService, Authentication, Notifications, ContainerService, ImageService, FormValidator, ModalService, RegistryService, SystemService, SettingsService, HttpRequestHelper) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  $scope.create = create;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $scope.formValues = {
 | 
					  $scope.formValues = {
 | 
				
			||||||
    alwaysPull: true,
 | 
					    alwaysPull: true,
 | 
				
			||||||
    Console: 'none',
 | 
					    Console: 'none',
 | 
				
			||||||
| 
						 | 
					@ -280,47 +282,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai
 | 
				
			||||||
    return config;
 | 
					    return config;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function confirmCreateContainer() {
 | 
					  
 | 
				
			||||||
    var deferred = $q.defer();
 | 
					 | 
				
			||||||
    Container.query({ all: 1, filters: {name: ['^/' + $scope.config.name + '$'] }}).$promise
 | 
					 | 
				
			||||||
    .then(function success(data) {
 | 
					 | 
				
			||||||
      var existingContainer = data[0];
 | 
					 | 
				
			||||||
      if (existingContainer) {
 | 
					 | 
				
			||||||
        ModalService.confirm({
 | 
					 | 
				
			||||||
          title: 'Are you sure ?',
 | 
					 | 
				
			||||||
          message: 'A container with the same name already exists. Portainer can automatically remove it and re-create one. Do you want to replace it?',
 | 
					 | 
				
			||||||
          buttons: {
 | 
					 | 
				
			||||||
            confirm: {
 | 
					 | 
				
			||||||
              label: 'Replace',
 | 
					 | 
				
			||||||
              className: 'btn-danger'
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
          callback: function onConfirm(confirmed) {
 | 
					 | 
				
			||||||
            if(!confirmed) { deferred.resolve(false); }
 | 
					 | 
				
			||||||
            else {
 | 
					 | 
				
			||||||
              // Remove old container
 | 
					 | 
				
			||||||
              ContainerService.remove(existingContainer, true)
 | 
					 | 
				
			||||||
              .then(function success(data) {
 | 
					 | 
				
			||||||
                Notifications.success('Container Removed', existingContainer.Id);
 | 
					 | 
				
			||||||
                deferred.resolve(true);
 | 
					 | 
				
			||||||
              })
 | 
					 | 
				
			||||||
              .catch(function error(err) {
 | 
					 | 
				
			||||||
                deferred.reject({ msg: 'Unable to remove container', err: err });
 | 
					 | 
				
			||||||
              });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        deferred.resolve(true);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    .catch(function error(err) {
 | 
					 | 
				
			||||||
      Notifications.error('Failure', err, 'Unable to retrieve containers');
 | 
					 | 
				
			||||||
      return undefined;
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    return deferred.promise;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  function loadFromContainerCmd(d) {
 | 
					  function loadFromContainerCmd(d) {
 | 
				
			||||||
    if ($scope.config.Cmd) {
 | 
					    if ($scope.config.Cmd) {
 | 
				
			||||||
      $scope.config.Cmd = ContainerHelper.commandArrayToString($scope.config.Cmd);
 | 
					      $scope.config.Cmd = ContainerHelper.commandArrayToString($scope.config.Cmd);
 | 
				
			||||||
| 
						 | 
					@ -628,62 +590,179 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai
 | 
				
			||||||
    return true;
 | 
					    return true;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $scope.create = function () {
 | 
					
 | 
				
			||||||
    confirmCreateContainer()
 | 
					  function create() {
 | 
				
			||||||
    .then(function success(confirm) {
 | 
					    var oldContainer = null;
 | 
				
			||||||
      if (!confirm) {
 | 
					
 | 
				
			||||||
        return false;
 | 
					    
 | 
				
			||||||
 | 
					    HttpRequestHelper.setPortainerAgentTargetHeader($scope.formValues.NodeName);
 | 
				
			||||||
 | 
					    return findCurrentContainer()
 | 
				
			||||||
 | 
					      .then(confirmCreateContainer)
 | 
				
			||||||
 | 
					      .then(startCreationProcess)
 | 
				
			||||||
 | 
					      .catch(notifyOnError)
 | 
				
			||||||
 | 
					      .finally(final);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function final() {
 | 
				
			||||||
 | 
					      $scope.state.actionInProgress = false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function findCurrentContainer() {
 | 
				
			||||||
 | 
					      return Container.query({ all: 1, filters: { name: ['^/' + $scope.config.name + '$'] } })
 | 
				
			||||||
 | 
					        .$promise
 | 
				
			||||||
 | 
					        .then(function onQuerySuccess(containers) {
 | 
				
			||||||
 | 
					          if (!containers.length) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          oldContainer = containers[0];
 | 
				
			||||||
 | 
					          return oldContainer;
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .catch(notifyOnError);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      function notifyOnError(err) {
 | 
				
			||||||
 | 
					        Notifications.error('Failure', err, 'Unable to retrieve containers');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function startCreationProcess(confirmed) {
 | 
				
			||||||
 | 
					      if (!confirmed) {
 | 
				
			||||||
 | 
					        return $q.when();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (!validateAccessControl()) {
 | 
				
			||||||
 | 
					        return $q.when();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      $scope.state.actionInProgress = true;
 | 
				
			||||||
 | 
					      return stopAndRenameContainer(oldContainer)
 | 
				
			||||||
 | 
					        .then(pullImageIfNeeded)
 | 
				
			||||||
 | 
					        .then(createNewContainer)
 | 
				
			||||||
 | 
					        .then(applyResourceControl)
 | 
				
			||||||
 | 
					        .then(connectToExtraNetworks)
 | 
				
			||||||
 | 
					        .then(removeOldContainer)
 | 
				
			||||||
 | 
					        .then(onSuccess);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function confirmCreateContainer(container) {
 | 
				
			||||||
 | 
					      if (!container) {
 | 
				
			||||||
 | 
					        return $q.when(true);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return showConfirmationModal();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      function showConfirmationModal() {
 | 
				
			||||||
 | 
					        var deferred = $q.defer();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ModalService.confirm({
 | 
				
			||||||
 | 
					          title: 'Are you sure ?',
 | 
				
			||||||
 | 
					          message: 'A container with the same name already exists. Portainer can automatically remove it and re-create one. Do you want to replace it?',
 | 
				
			||||||
 | 
					          buttons: {
 | 
				
			||||||
 | 
					            confirm: {
 | 
				
			||||||
 | 
					              label: 'Replace',
 | 
				
			||||||
 | 
					              className: 'btn-danger'
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          callback: function onConfirm(confirmed) {
 | 
				
			||||||
 | 
					            deferred.resolve(confirmed);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return deferred.promise;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function stopAndRenameContainer(oldContainer) {
 | 
				
			||||||
 | 
					      if (!oldContainer) {
 | 
				
			||||||
 | 
					        return $q.when();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return stopContainerIfNeeded(oldContainer)
 | 
				
			||||||
 | 
					        .then(renameContainer);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function stopContainerIfNeeded(oldContainer) {
 | 
				
			||||||
 | 
					      if (oldContainer.State !== 'running') {
 | 
				
			||||||
 | 
					        return $q.when();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return ContainerService.stopContainer(oldContainer.Id);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function renameContainer() {
 | 
				
			||||||
 | 
					      return ContainerService.renameContainer(oldContainer.Id, oldContainer.Names[0].substring(1) + '-old');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function pullImageIfNeeded() {
 | 
				
			||||||
 | 
					      return $q.when($scope.formValues.alwaysPull &&
 | 
				
			||||||
 | 
					        ImageService.pullImage($scope.config.Image, $scope.formValues.Registry, true));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function createNewContainer() {
 | 
				
			||||||
 | 
					      var config = prepareConfiguration();
 | 
				
			||||||
 | 
					      return ContainerService.createAndStartContainer(config);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function applyResourceControl(newContainer) {
 | 
				
			||||||
 | 
					      var containerIdentifier = newContainer.Id;
 | 
				
			||||||
 | 
					      var userId = Authentication.getUserDetails().ID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return $q.when(ResourceControlService.applyResourceControl(
 | 
				
			||||||
 | 
					        'container',
 | 
				
			||||||
 | 
					        containerIdentifier,
 | 
				
			||||||
 | 
					        userId,
 | 
				
			||||||
 | 
					        $scope.formValues.AccessControlData, []
 | 
				
			||||||
 | 
					      )).then(function onApplyResourceControlSuccess() {
 | 
				
			||||||
 | 
					        return containerIdentifier;
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function connectToExtraNetworks(newContainerId) {
 | 
				
			||||||
 | 
					      if (!$scope.extraNetworks) {
 | 
				
			||||||
 | 
					        return $q.when();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      var connectionPromises = Object.keys($scope.extraNetworks).map(function (networkName) {
 | 
				
			||||||
 | 
					        return NetworkService.connectContainer(networkName, newContainerId);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return $q.all(connectionPromises);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function removeOldContainer() {
 | 
				
			||||||
 | 
					      var deferred = $q.defer();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (!oldContainer) {
 | 
				
			||||||
 | 
					        deferred.resolve();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      ContainerService.remove(oldContainer, true)
 | 
				
			||||||
 | 
					        .then(notifyOnRemoval)
 | 
				
			||||||
 | 
					        .catch(notifyOnRemoveError);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return deferred.promise;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      function notifyOnRemoval() {
 | 
				
			||||||
 | 
					        Notifications.success('Container Removed', oldContainer.Id);
 | 
				
			||||||
 | 
					        deferred.resolve();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      function notifyOnRemoveError(err) {
 | 
				
			||||||
 | 
					        deferred.reject({ msg: 'Unable to remove container', err: err });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function notifyOnError(err) {
 | 
				
			||||||
 | 
					      Notifications.error('Failure', err, 'Unable to create container');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function validateAccessControl() {
 | 
				
			||||||
      var accessControlData = $scope.formValues.AccessControlData;
 | 
					      var accessControlData = $scope.formValues.AccessControlData;
 | 
				
			||||||
      var userDetails = Authentication.getUserDetails();
 | 
					      var userDetails = Authentication.getUserDetails();
 | 
				
			||||||
      var isAdmin = userDetails.role === 1;
 | 
					      var isAdmin = userDetails.role === 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (!validateForm(accessControlData, isAdmin)) {
 | 
					      return validateForm(accessControlData, isAdmin);
 | 
				
			||||||
        return;
 | 
					    }
 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      $scope.state.actionInProgress = true;
 | 
					    function onSuccess() {
 | 
				
			||||||
      var config = prepareConfiguration();
 | 
					      Notifications.success('Container successfully created');
 | 
				
			||||||
      var nodeName = $scope.formValues.NodeName;
 | 
					      $state.go('docker.containers', {}, { reload: true });
 | 
				
			||||||
      HttpRequestHelper.setPortainerAgentTargetHeader(nodeName);
 | 
					    }
 | 
				
			||||||
      createContainer(config, accessControlData);
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    .catch(function error(err) {
 | 
					 | 
				
			||||||
      Notifications.error('Failure', err, 'Unable to create container');
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  function createContainer(config, accessControlData) {
 | 
					 | 
				
			||||||
    var containerIdentifier;
 | 
					 | 
				
			||||||
    $q.when(!$scope.formValues.alwaysPull || ImageService.pullImage($scope.config.Image, $scope.formValues.Registry, true))
 | 
					 | 
				
			||||||
    .finally(function final() {
 | 
					 | 
				
			||||||
      ContainerService.createAndStartContainer(config)
 | 
					 | 
				
			||||||
      .then(function success(data) {
 | 
					 | 
				
			||||||
        containerIdentifier = data.Id;
 | 
					 | 
				
			||||||
        var userId = Authentication.getUserDetails().ID;
 | 
					 | 
				
			||||||
        return ResourceControlService.applyResourceControl('container', containerIdentifier, userId, accessControlData, []);
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .then(function success() {
 | 
					 | 
				
			||||||
        if($scope.extraNetworks) {
 | 
					 | 
				
			||||||
          return $q.all(
 | 
					 | 
				
			||||||
            Object.keys($scope.extraNetworks).map(function(networkName) {
 | 
					 | 
				
			||||||
              return NetworkService.connectContainer(networkName, containerIdentifier);
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
          );
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .then(function success() {
 | 
					 | 
				
			||||||
        Notifications.success('Container successfully created');
 | 
					 | 
				
			||||||
        $state.go('docker.containers', {}, {reload: true});
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .catch(function error(err) {
 | 
					 | 
				
			||||||
        Notifications.error('Failure', err, 'Unable to create container');
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .finally(function final() {
 | 
					 | 
				
			||||||
        $scope.state.actionInProgress = false;
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  initView();
 | 
					  initView();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -130,6 +130,7 @@
 | 
				
			||||||
                <span ng-hide="state.actionInProgress">Deploy the container</span>
 | 
					                <span ng-hide="state.actionInProgress">Deploy the container</span>
 | 
				
			||||||
                <span ng-show="state.actionInProgress">Deployment in progress...</span>
 | 
					                <span ng-show="state.actionInProgress">Deployment in progress...</span>
 | 
				
			||||||
              </button>
 | 
					              </button>
 | 
				
			||||||
 | 
					              <span class="text-danger" ng-if="state.formValidationError" style="margin-left: 5px;">{{ state.formValidationError }}</span>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
          <!-- !actions -->
 | 
					          <!-- !actions -->
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue