portainer/portainer

View on GitHub
app/docker/views/networks/create/createNetworkController.js

Summary

Maintainability
D
2 days
Test Coverage
import _ from 'lodash-es';
import { AccessControlFormData } from '../../../../portainer/components/accessControlForm/porAccessControlFormModel';
import { MacvlanFormData } from '../../../components/network-macvlan-form/networkMacvlanFormModel';

angular.module('portainer.docker').controller('CreateNetworkController', [
  '$q',
  '$scope',
  '$state',
  'PluginService',
  'Notifications',
  'NetworkService',
  'LabelHelper',
  'Authentication',
  'ResourceControlService',
  'FormValidator',
  'HttpRequestHelper',
  'endpoint',
  function ($q, $scope, $state, PluginService, Notifications, NetworkService, LabelHelper, Authentication, ResourceControlService, FormValidator, HttpRequestHelper, endpoint) {
    $scope.endpoint = endpoint;

    $scope.formValues = {
      DriverOptions: [],
      IPV4: {
        Subnet: '',
        Gateway: '',
        IPRange: '',
        AuxiliaryAddresses: [],
      },
      IPV6: {
        Subnet: '',
        Gateway: '',
        IPRange: '',
        AuxiliaryAddresses: [],
      },
      Labels: [],
      AccessControlData: new AccessControlFormData(),
      NodeName: null,
      Macvlan: new MacvlanFormData(),
    };

    $scope.state = {
      formValidationError: '',
      actionInProgress: false,
    };

    $scope.availableNetworkDrivers = [];

    $scope.config = {
      Driver: 'bridge',
      CheckDuplicate: true,
      Internal: false,
      Attachable: false,
      EnableIPv6: false,
      // Force IPAM Driver to 'default', should not be required.
      // See: https://github.com/docker/docker/issues/25735
      IPAM: {
        Driver: 'default',
        Config: [],
      },
      Labels: {},
    };

    $scope.addDriverOption = function () {
      $scope.formValues.DriverOptions.push({
        name: '',
        value: '',
      });
    };

    $scope.removeDriverOption = function (index) {
      $scope.formValues.DriverOptions.splice(index, 1);
    };

    $scope.addLabel = function () {
      $scope.formValues.Labels.push({
        key: '',
        value: '',
      });
    };

    $scope.removeLabel = function (index) {
      $scope.formValues.Labels.splice(index, 1);
    };

    $scope.addIPV4AuxAddress = function () {
      $scope.formValues.IPV4.AuxiliaryAddresses.push('');
    };

    $scope.addIPV6AuxAddress = function () {
      $scope.formValues.IPV6.AuxiliaryAddresses.push('');
    };

    $scope.removeIPV4AuxAddress = function (index) {
      $scope.formValues.IPV4.AuxiliaryAddresses.splice(index, 1);
      $scope.state.IPV4AuxiliaryAddressesError.splice(index, 1);
    };

    $scope.removeIPV6AuxAddress = function (index) {
      $scope.formValues.IPV6.AuxiliaryAddresses.splice(index, 1);
      $scope.state.IPV6AuxiliaryAddressesError.splice(index, 1);
    };

    function checkAuxiliaryAddress(excludedIP, gateway) {
      const split = _.split(excludedIP, '=');

      if (split.length === 2) {
        return split[1] === gateway;
      }
      return excludedIP === gateway;
    }

    $scope.checkIPV4AuxiliaryAddress = function (index) {
      $scope.state.IPV4AuxiliaryAddressesError[index] = checkAuxiliaryAddress($scope.formValues.IPV4.AuxiliaryAddresses[index], $scope.formValues.IPV4.Gateway);
    };

    $scope.checkIPV6AuxiliaryAddress = function (index) {
      $scope.state.IPV6AuxiliaryAddressesError[index] = checkAuxiliaryAddress($scope.formValues.IPV6.AuxiliaryAddresses[index], $scope.formValues.IPV6.Gateway);
    };

    $scope.isValid = function () {
      const validIPV4 = !_.reduce($scope.state.IPV4AuxiliaryAddressesError, (acc, item) => acc || item, false);
      const validIPV6 = !_.reduce($scope.state.IPV6AuxiliaryAddressesError, (acc, item) => acc || item, false);
      return validIPV4 && validIPV6;
    };

    function prepareAuxiliaryAddresses(ipamConfig, ipFormValues) {
      ipamConfig.AuxiliaryAddresses = {};
      _.forEach(ipFormValues.AuxiliaryAddresses, (auxAddress, index) => {
        const split = _.split(auxAddress, '=');
        if (split.length === 2) {
          ipamConfig.AuxiliaryAddresses[split[0]] = split[1];
        } else {
          ipamConfig.AuxiliaryAddresses['device' + index] = auxAddress;
        }
      });
    }

    function prepareIPAMConfiguration(config) {
      if ($scope.formValues.IPV4.Subnet) {
        let ipamConfig = {};
        ipamConfig.Subnet = $scope.formValues.IPV4.Subnet;
        if ($scope.formValues.IPV4.Gateway) {
          ipamConfig.Gateway = $scope.formValues.IPV4.Gateway;
        }
        if ($scope.formValues.IPV4.IPRange) {
          ipamConfig.IPRange = $scope.formValues.IPV4.IPRange;
        }
        if ($scope.formValues.IPV4.AuxiliaryAddresses.length) {
          prepareAuxiliaryAddresses(ipamConfig, $scope.formValues.IPV4);
        }
        config.IPAM.Config.push(ipamConfig);
      }
      if ($scope.formValues.IPV6.Subnet) {
        let ipamConfig = {};
        ipamConfig.Subnet = $scope.formValues.IPV6.Subnet;
        if ($scope.formValues.IPV6.Gateway) {
          ipamConfig.Gateway = $scope.formValues.IPV6.Gateway;
        }
        if ($scope.formValues.IPV6.IPRange) {
          ipamConfig.IPRange = $scope.formValues.IPV6.IPRange;
        }
        if ($scope.formValues.IPV6.AuxiliaryAddresses.length) {
          prepareAuxiliaryAddresses(ipamConfig, $scope.formValues.IPV6);
        }
        config.EnableIPv6 = true;
        config.IPAM.Config.push(ipamConfig);
      }
    }

    function prepareDriverOptions(config) {
      var options = {};
      $scope.formValues.DriverOptions.forEach(function (option) {
        options[option.name] = option.value;
      });
      config.Options = options;
    }

    function prepareLabelsConfig(config) {
      config.Labels = LabelHelper.fromKeyValueToLabelHash($scope.formValues.Labels);
    }

    function prepareConfiguration() {
      var config = angular.copy($scope.config);
      prepareIPAMConfiguration(config);
      prepareDriverOptions(config);
      prepareLabelsConfig(config);
      return config;
    }

    function modifyNetworkConfigurationForMacvlanConfigOnly(config) {
      config.Internal = null;
      config.Attachable = null;
      config.ConfigOnly = true;
      config.Options.parent = $scope.formValues.Macvlan.ParentNetworkCard;
    }

    function modifyNetworkConfigurationForMacvlanConfigFrom(config, selectedNetworkConfig) {
      config.ConfigFrom = {
        Network: selectedNetworkConfig.Name,
      };
      if ($scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE') {
        config.Scope = 'swarm';
      } else {
        config.Scope = 'local';
      }
    }

    $scope.onChangeInternal = function (enable) {
      $scope.$evalAsync(() => {
        $scope.config.Internal = enable;
      });
    };

    $scope.onChangeAttachable = function (enable) {
      $scope.$evalAsync(() => {
        $scope.config.Attachable = enable;
      });
    };

    function validateForm(accessControlData, isAdmin) {
      $scope.state.formValidationError = '';
      var error = '';
      error = FormValidator.validateAccessControl(accessControlData, isAdmin);

      if (error) {
        $scope.state.formValidationError = error;
        return false;
      }
      return true;
    }

    function createNetwork(context) {
      HttpRequestHelper.setPortainerAgentTargetHeader(context.nodeName);
      HttpRequestHelper.setPortainerAgentManagerOperation(context.managerOperation);

      $scope.state.actionInProgress = true;
      NetworkService.create(context.networkConfiguration)
        .then(function success(data) {
          const userId = context.userDetails.ID;
          const accessControlData = context.accessControlData;
          const resourceControl = data.Portainer.ResourceControl;
          return ResourceControlService.applyResourceControl(userId, accessControlData, resourceControl);
        })
        .then(function success() {
          Notifications.success('Success', 'Network successfully created');
          if (context.reload) {
            $state.go(
              'docker.networks',
              {},
              {
                reload: true,
              }
            );
          }
        })
        .catch(function error(err) {
          Notifications.error('Failure', err, 'An error occurred during network creation');
        })
        .finally(function final() {
          $scope.state.actionInProgress = false;
        });
    }

    $scope.create = function () {
      var networkConfiguration = prepareConfiguration();
      var accessControlData = $scope.formValues.AccessControlData;
      var userDetails = Authentication.getUserDetails();
      var isAdmin = Authentication.isAdmin();

      if (!validateForm(accessControlData, isAdmin)) {
        return;
      }

      var creationContext = {
        nodeName: $scope.formValues.NodeName,
        managerOperation: false,
        networkConfiguration: networkConfiguration,
        userDetails: userDetails,
        accessControlData: accessControlData,
        reload: true,
      };

      if ($scope.applicationState.endpoint.mode.agentProxy && $scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && $scope.config.Driver === 'overlay') {
        creationContext.managerOperation = true;
      }

      if ($scope.config.Driver === 'macvlan') {
        if ($scope.formValues.Macvlan.Scope === 'local') {
          modifyNetworkConfigurationForMacvlanConfigOnly(networkConfiguration);
        } else if ($scope.formValues.Macvlan.Scope === 'swarm') {
          var selectedNetworkConfig = $scope.formValues.Macvlan.SelectedNetworkConfig;
          modifyNetworkConfigurationForMacvlanConfigFrom(networkConfiguration, selectedNetworkConfig);
          creationContext.nodeName = selectedNetworkConfig.NodeName;
        }
      }

      if (
        $scope.config.Driver === 'macvlan' &&
        $scope.formValues.Macvlan.Scope === 'local' &&
        $scope.applicationState.endpoint.mode.agentProxy &&
        $scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE'
      ) {
        var selectedNodes = $scope.formValues.Macvlan.DatatableState.selectedItems;
        selectedNodes.forEach(function (node, idx) {
          creationContext.nodeName = node.Hostname;
          creationContext.reload = idx === selectedNodes.length - 1 ? true : false;
          createNetwork(creationContext);
        });
      } else {
        createNetwork(creationContext);
      }
    };

    function initView() {
      var apiVersion = $scope.applicationState.endpoint.apiVersion;
      $scope.state.IPV4AuxiliaryAddressesError = [];
      $scope.state.IPV6AuxiliaryAddressesError = [];

      PluginService.networkPlugins(apiVersion < 1.25)
        .then(function success(data) {
          $scope.availableNetworkDrivers = data;
          $scope.availableNetworkDrivers = _.filter($scope.availableNetworkDrivers, (driver) => driver !== 'host' && driver !== 'null');
        })
        .catch(function error(err) {
          Notifications.error('Failure', err, 'Unable to retrieve network drivers');
        });
    }

    initView();
  },
]);