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',
 | 
			
		||||
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 = {
 | 
			
		||||
    alwaysPull: true,
 | 
			
		||||
    Console: 'none',
 | 
			
		||||
| 
						 | 
				
			
			@ -280,46 +282,6 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai
 | 
			
		|||
    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) {
 | 
			
		||||
    if ($scope.config.Cmd) {
 | 
			
		||||
| 
						 | 
				
			
			@ -628,62 +590,179 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai
 | 
			
		|||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  $scope.create = function () {
 | 
			
		||||
    confirmCreateContainer()
 | 
			
		||||
    .then(function success(confirm) {
 | 
			
		||||
      if (!confirm) {
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
  function create() {
 | 
			
		||||
    var oldContainer = null;
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    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 userDetails = Authentication.getUserDetails();
 | 
			
		||||
      var isAdmin = userDetails.role === 1;
 | 
			
		||||
 | 
			
		||||
      if (!validateForm(accessControlData, isAdmin)) {
 | 
			
		||||
        return;
 | 
			
		||||
      return validateForm(accessControlData, isAdmin);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
      $scope.state.actionInProgress = true;
 | 
			
		||||
      var config = prepareConfiguration();
 | 
			
		||||
      var nodeName = $scope.formValues.NodeName;
 | 
			
		||||
      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() {
 | 
			
		||||
    function onSuccess() {
 | 
			
		||||
      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;
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
      $state.go('docker.containers', {}, { reload: true });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  initView();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -130,6 +130,7 @@
 | 
			
		|||
                <span ng-hide="state.actionInProgress">Deploy the container</span>
 | 
			
		||||
                <span ng-show="state.actionInProgress">Deployment in progress...</span>
 | 
			
		||||
              </button>
 | 
			
		||||
              <span class="text-danger" ng-if="state.formValidationError" style="margin-left: 5px;">{{ state.formValidationError }}</span>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
          <!-- !actions -->
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue