Commit 5d8e479b authored by Ryo Yamashita's avatar Ryo Yamashita Committed by Yas Naoi
Browse files

Issue #3280044 by Ryo Yamashita, yas, Xiaohua Guan, baldwinlouie: Refactor OpenStackImageEditForm

parent 4b8d276d
Loading
Loading
Loading
Loading
+25 −63
Changes for modules/cloud_service_providers/openstack/src/Controller/ApiController.php: 25 added lines, 63 removed lines.
Original line number Diff line number Diff line
@@ -12,7 +12,6 @@ use Drupal\Core\Messenger\Messenger;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\openstack\Entity\IntermediateFormState;
use Drupal\openstack\Service\Ec2\OpenStackEc2OperationsService;
use Drupal\openstack\Service\OpenStackOperationsServiceInterface;
use Drupal\openstack\Service\OpenStackServiceFactoryInterface;
use Drupal\views\Views;
@@ -487,6 +486,9 @@ class ApiController extends ControllerBase implements ApiControllerInterface {
   * {@inheritdoc}
   */
  public function operateEntity(Request $request, string $cloud_context, string $entity_type_id, string $entity_id, string $command): JsonResponse {
    // Create an instance of the entity.
    // The process splits between adding a new entity
    // and when reading an existing entity.
    $entity = $command !== 'create'
      ? $this->entityTypeManager
        ->getStorage($entity_type_id)
@@ -503,82 +505,42 @@ class ApiController extends ControllerBase implements ApiControllerInterface {
      ], 404);
    }

    if ($command === 'create' && $entity_type_id === 'openstack_image') {
    // Depending on the content of the process,
    // branching is performed with a switch statement.
    /**
     * @var \Drupal\aws_cloud\Entity\Ec2\ImageInterface|\Drupal\aws_cloud\Entity\Ec2\Image
     */
    $original_entity = $entity;
    $form_state = new IntermediateFormState();

    $method_name = '';
    switch ($command . '.' . $entity_type_id) {
      case 'create_openstack_image':
        $original_entity->setName($request->get('name', ''));
        $original_entity->setVisibility(!empty($request->get('visibility', '')) ? 'public' : 'private');
        $form_state->set('name', $request->get('name', ''));
        $method_name = 'createOpenStackImage';
        break;

      $result = $this->openStackOperationsService->createOpenStackImage(
        $original_entity,
        [],
        $form_state
      );
      return !empty($result)
        ? new JsonResponse([
          'result' => 'OK',
        ], 200)
        : new JsonResponse([
          'result' => 'NG',
          'reason' => 'Internal Server Error',
        ], 500);
    }

    if ($command === 'delete' && $entity_type_id === 'openstack_image') {
      $form_state = new IntermediateFormState();

      $result = $this->openStackOperationsService->deleteOpenStackImage(
        $entity,
        [],
        $form_state
      );
      return !empty($result)
        ? new JsonResponse([
          'result' => 'OK',
        ], 200)
        : new JsonResponse([
          'result' => 'NG',
          'reason' => 'Internal Server Error',
        ], 500);
    }

    $method_name = '';
    $parameter = [];
    switch ($command) {
      case 'edit':
        switch ($entity_type_id) {
          case 'openstack_image':
            $method_name = 'editImage';
            $parameter = [
              'name' => $request->get('name', ''),
            ];
            $entity->setName($request->get('name', ''));
            $entity->setVisibility(!empty($request->get('visibility', '')) ? 'public' : 'private');
      case 'edit_openstack_image':
        $original_entity->setName($request->get('name', ''));
        $original_entity->setVisibility(!empty($request->get('visibility', '')) ? 'public' : 'private');
        $method_name = 'editOpenStackImage';
        break;
        }

      case 'delete_openstack_image':
        $method_name = 'deleteOpenStackImage';
        break;

    }

    $result = FALSE;
    if (!empty($method_name)) {
      $service = new OpenStackEc2OperationsService(
        $this->entityTypeManager,
        \Drupal::service('aws_cloud.ec2'),
        $this->cloudConfigPluginManager,
        $this->cloudService,
        $this->openStackServiceFactory,
    // Execute the process.
    $result = NULL;
    if (method_exists($this->openStackOperationsService, $method_name)) {
      $result = $this->openStackOperationsService->$method_name(
        $original_entity,
        [],
        $form_state
      );
      $ec2Service = $this->openStackServiceFactory->get($cloud_context);
      $service->setEc2Service($ec2Service);

      if (method_exists($service, $method_name)) {
        $result = $service->$method_name($entity, $parameter);
      }
    }

    return !empty($result)
+0 −13
Changes for modules/cloud_service_providers/openstack/src/Form/OpenStackImageCreateForm.php: 0 added lines, 13 removed lines.
Original line number Diff line number Diff line
@@ -19,7 +19,6 @@ use Drupal\Core\Plugin\CachedDiscoveryClearerInterface;
use Drupal\Core\Render\Renderer;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\openstack\Service\Ec2\OpenStackEc2OperationsServiceInterface;
use Drupal\openstack\Service\OpenStackOperationsServiceInterface;
use Drupal\openstack\Service\OpenStackServiceFactoryInterface;
use Drupal\openstack\Service\Rest\OpenStackService as OpenStackRestService;
@@ -39,13 +38,6 @@ class OpenStackImageCreateForm extends ImageCreateForm {
   */
  protected $openStackServiceFactory;

  /**
   * The OpenStack operation service.
   *
   * @var \Drupal\openstack\Service\Ec2\OpenStackEc2OperationsServiceInterface
   */
  private $openStackEc2OperationsService;

  /**
   * The OpenStack operations Service.
   *
@@ -60,8 +52,6 @@ class OpenStackImageCreateForm extends ImageCreateForm {
   *   Object for interfacing with OpenStack Service.
   * @param \Drupal\aws_cloud\Service\Ec2\Ec2ServiceInterface $ec2_service
   *   The AWS Cloud or OpenStack EC2 Service.
   * @param \Drupal\openstack\Service\Ec2\OpenStackEc2OperationsServiceInterface $openstack_ec2_operations_service
   *   The OpenStack operation service.
   * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
   *   The entity repository service.
   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
@@ -96,7 +86,6 @@ class OpenStackImageCreateForm extends ImageCreateForm {
  public function __construct(
    OpenStackServiceFactoryInterface $openstack_service_factory,
    Ec2ServiceInterface $ec2_service,
    OpenStackEc2OperationsServiceInterface $openstack_ec2_operations_service,
    EntityRepositoryInterface $entity_repository,
    EntityTypeBundleInfoInterface $entity_type_bundle_info,
    TimeInterface $time,
@@ -133,7 +122,6 @@ class OpenStackImageCreateForm extends ImageCreateForm {
    );

    $this->openStackServiceFactory = $openstack_service_factory;
    $this->openStackEc2OperationsService = $openstack_ec2_operations_service;
    $this->openStackOperationsService = $openstack_operations_service;
  }

@@ -150,7 +138,6 @@ class OpenStackImageCreateForm extends ImageCreateForm {
    return new static(
      $container->get('openstack.factory'),
      $container->get('openstack.ec2'),
      $container->get('openstack.ec2_operations'),
      $container->get('entity.repository'),
      $container->get('entity_type.bundle.info'),
      $container->get('datetime.time'),
+15 −22
Changes for modules/cloud_service_providers/openstack/src/Form/OpenStackImageEditForm.php: 15 added lines, 22 removed lines.
Original line number Diff line number Diff line
@@ -20,7 +20,7 @@ use Drupal\Core\Render\Renderer;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\cloud\Service\CloudServiceInterface;
use Drupal\openstack\Service\Ec2\OpenStackEc2OperationsServiceInterface;
use Drupal\openstack\Service\OpenStackOperationsServiceInterface;
use Drupal\openstack\Service\OpenStackServiceFactoryInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

@@ -39,11 +39,11 @@ class OpenStackImageEditForm extends ImageEditForm {
  protected $openStackServiceFactory;

  /**
   * The OpenStack operation service.
   * The OpenStack operations Service.
   *
   * @var \Drupal\openstack\Service\Ec2\OpenStackEc2OperationsServiceInterface
   * @var \Drupal\openstack\Service\OpenStackOperationsServiceInterface
   */
  private $openStackEc2OperationsService;
  protected $openStackOperationsService;

  /**
   * OpenStackImageEditForm constructor.
@@ -52,8 +52,6 @@ class OpenStackImageEditForm extends ImageEditForm {
   *   Object for interfacing with OpenStack Service.
   * @param \Drupal\aws_cloud\Service\Ec2\Ec2ServiceInterface $ec2_service
   *   The AWS Cloud or OpenStack EC2 Service.
   * @param \Drupal\openstack\Service\Ec2\OpenStackEc2OperationsServiceInterface $openstack_operations_service
   *   The OpenStack operation service.
   * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
   *   The entity repository service.
   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
@@ -82,11 +80,12 @@ class OpenStackImageEditForm extends ImageEditForm {
   *   The general renderer.
   * @param \Drupal\cloud\Service\CloudServiceInterface $cloud_service
   *   The Cloud service.
   * @param \Drupal\openstack\Service\OpenStackOperationsServiceInterface $openstack_operations_service
   *   The OpenStack Operations service.
   */
  public function __construct(
    OpenStackServiceFactoryInterface $openstack_service_factory,
    Ec2ServiceInterface $ec2_service,
    OpenStackEc2OperationsServiceInterface $openstack_operations_service,
    EntityRepositoryInterface $entity_repository,
    EntityTypeBundleInfoInterface $entity_type_bundle_info,
    TimeInterface $time,
@@ -100,7 +99,8 @@ class OpenStackImageEditForm extends ImageEditForm {
    RouteMatchInterface $route_match,
    DateFormatterInterface $date_formatter,
    Renderer $renderer,
    CloudServiceInterface $cloud_service
    CloudServiceInterface $cloud_service,
    OpenStackOperationsServiceInterface $openstack_operations_service
  ) {

    parent::__construct(
@@ -122,7 +122,7 @@ class OpenStackImageEditForm extends ImageEditForm {
    );

    $this->openStackServiceFactory = $openstack_service_factory;
    $this->openStackEc2OperationsService = $openstack_operations_service;
    $this->openStackOperationsService = $openstack_operations_service;
  }

  /**
@@ -138,7 +138,6 @@ class OpenStackImageEditForm extends ImageEditForm {
    return new static(
      $container->get('openstack.factory'),
      $container->get('openstack.ec2'),
      $container->get('openstack.ec2_operations'),
      $container->get('entity.repository'),
      $container->get('entity_type.bundle.info'),
      $container->get('datetime.time'),
@@ -152,7 +151,8 @@ class OpenStackImageEditForm extends ImageEditForm {
      $container->get('current_route_match'),
      $container->get('date.formatter'),
      $container->get('renderer'),
      $container->get('cloud')
      $container->get('cloud'),
      $container->get('openstack.operations')
    );
  }

@@ -186,9 +186,11 @@ class OpenStackImageEditForm extends ImageEditForm {
        '#prefix'        => '<div class="container-inline">',
      ];

      /** @var \Drupal\openstack\Entity\OpenStackImage $entity */
      $entity = $this->entity;
      $form['image']['launch_permission']['visibility'] = [
        '#type' => 'radios',
        '#default_value' => $this->entity->getVisibility() ? '1' : '0',
        '#default_value' => $entity->getVisibility() ? '1' : '0',
        '#options' => [
          '0' => $this->t('Private'),
          '1' => $this->t('Public'),
@@ -216,16 +218,7 @@ class OpenStackImageEditForm extends ImageEditForm {
   *   The current form state.
   */
  public function save(array $form, FormStateInterface $form_state): void {
    $this->copyFormItemValues($form);
    $this->trimTextfields($form, $form_state);

    $form_values = $form_state->getValues();
    $visibility = array_key_exists('visibility', $form_values)
      ? $form_values['visibility'] : NULL;

    $this->openStackEc2OperationsService->editImage($this->entity, [
      'visibility' => $visibility,
    ]);
    $this->openStackOperationsService->editOpenStackImage($this->entity, $form, $form_state);
  }

}
+317 −0
Changes for modules/cloud_service_providers/openstack/src/Service/OpenStackOperationsService.php: 317 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@ use Drupal\aws_cloud\Entity\Ec2\ImageInterface;
use Drupal\aws_cloud\Service\Ec2\Ec2ServiceException;
use Drupal\aws_cloud\Service\Ec2\Ec2ServiceInterface;
use Drupal\aws_cloud\Traits\AwsCloudEntityCheckTrait;
use Drupal\cloud\Entity\CloudConfigInterface;
use Drupal\cloud\Plugin\cloud\config\CloudConfigPluginManagerInterface;
use Drupal\cloud\Service\CloudServiceInterface;
use Drupal\cloud\Traits\CloudContentEntityTrait;
@@ -339,6 +340,322 @@ class OpenStackOperationsService implements OpenStackOperationsServiceInterface
    }
  }

  /**
   * Copy values from #type=item elements to its original element type.
   *
   * @param array $form
   *   The form array.
   */
  private function copyFormItemValues(array $form): void {
    /**
     * @var \Drupal\Core\Entity\ContentEntityInterface
     */
    $original_entity = $this->entityTypeManager
      ->getStorage($this->entity->getEntityTypeId())
      ->load($this->entity->id());

    $item_field_names = [];
    foreach ($form ?: [] as $name => $item) {
      if (!is_array($item)) {
        continue;
      }

      if (isset($item['#type'])
        && $item['#type'] === 'item'
        && (!isset($item['#not_field']) || $item['#not_field'] === FALSE)
      ) {
        $item_field_names[] = $name;
      }

      if (isset($item['#type']) && $item['#type'] === 'details') {
        foreach ($item ?: [] as $sub_item_name => $sub_item) {
          if (is_array($sub_item)
            && isset($sub_item['#type'])
            && $sub_item['#type'] === 'item'
            && (!isset($sub_item['#not_field']) || $sub_item['#not_field'] === FALSE)
          ) {
            $item_field_names[] = $sub_item_name;
          }
        }
      }
    }

    foreach ($item_field_names ?: [] as $item_field_name) {
      // Support multi-valued item fields.
      $values = !empty($original_entity) ? $original_entity->get($item_field_name)->getValue() : [];
      if (!empty($values) && count($values) > 1) {
        $item_field_values = array_map(static function ($value) {
          return $value['value'];
        }, $values);
        $this->entity->set($item_field_name, $item_field_values);
      }

      $item_field_value = !empty($original_entity) && !empty($original_entity->get($item_field_name))
        ? $original_entity->get($item_field_name)->value
        : [];
      if (!empty($item_field_value)) {
        $this->entity->set($item_field_name, $item_field_value);
      }
    }
  }

  /**
   * Save a cloud content.
   *
   * @param array $form
   *   An associative array containing the structure of the form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   *
   * @throws \Drupal\Core\Entity\EntityMalformedException
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  private function saveCloudContent(array $form, FormStateInterface $form_state) {
    /**
     * The entity being used by this form.
     *
     * @var \Drupal\aws_cloud\Entity\Ec2\ImageInterface
     */
    $entity = $this->entity;

    if (!($entity instanceof CloudConfigInterface)) {
      $this->cloudConfigPluginManager->setCloudContext($entity->getCloudContext());
      $cloud_config = $this->cloudConfigPluginManager->loadConfigEntity();
    }

    if (!$entity->isNew() && !($entity instanceof CloudConfigInterface) && $cloud_config->isRemote()) {
      $status = SAVED_UPDATED;
      $passive_operation = 'updated remotely';
      $this->processOperationStatus($entity, $passive_operation);
      return $status;
    }

    $status = $entity->save();

    // Add a updated status message and the log.
    switch ($status) {
      case SAVED_NEW:
        $passive_operation = 'created';
        break;

      case SAVED_DELETED:
        $passive_operation = 'deleted';
        break;

      default:
      case SAVED_UPDATED:
        $passive_operation = 'updated';
    }

    if (!($entity instanceof CloudConfigInterface) && $cloud_config->isRemote()) {
      $passive_operation .= ' remotely';
    }

    $this->processOperationStatus($entity, $passive_operation);

    // Clear block and menu cache.
    $this->clearCacheValues($entity->getCacheTags());

    return $status;
  }

  /**
   * Save an AWS Cloud content.
   *
   * @param array $form
   *   An associative array containing the structure of the form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   *
   * @throws \Drupal\Core\Entity\EntityMalformedException
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  private function saveAwsCloudContent(array $form, FormStateInterface $form_state): void {
    // Copy values of the form elements whose type are item to entity.
    // If not, the properties corresponding to the form elements
    // will be saved as NULL.
    $this->copyFormItemValues($form);

    $this->trimTextfields($form, $form_state);

    $this->saveCloudContent($form, $form_state);
  }

  /**
   * Save an Image.
   *
   * @param array $form
   *   An associative array containing the structure of the form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   *
   * @throws \Drupal\Core\Entity\EntityMalformedException
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  private function saveImage(array $form, FormStateInterface $form_state): void {
    /**
     * The entity being used by this form.
     *
     * @var \Drupal\aws_cloud\Entity\Ec2\ImageInterface|\Drupal\aws_cloud\Entity\Ec2\Image
     */
    $entity = $this->entity;

    /**
     * The original entity data.
     *
     * @var \Drupal\aws_cloud\Entity\Ec2\ImageInterface|\Drupal\aws_cloud\Entity\Ec2\Image
     */
    $original_entity = $this->entityTypeManager
      ->getStorage($entity->getEntityTypeId())
      ->load($entity->id());

    $this->saveAwsCloudContent($form, $form_state);

    $this->updateNameAndCreatedByTags($entity, $entity->getImageId());

    $this->cloudConfigPluginManager->setCloudContext($entity->getCloudContext());
    $cloud_config = $this->cloudConfigPluginManager->loadConfigEntity();

    // Call 'modifyImageAttribute'
    // only when OwnerId is same as Account ID of Cloud Config.
    // It's to avoid AuthFailure error from AWS API.
    if ($cloud_config->get('field_account_id')->value !== $entity->getAccountId()) {
      return;
    }

    $form_values = $form_state->getValues();
    $visibility = $form_values['visibility'] ?? 0;

    $permission = [
      [
        'Group' => 'all',
        'UserId' => $entity->getAccountId(),
      ],
    ];

    // 1 => Public, 0 => Private.
    $launch_permission =
      $visibility
        ? ['Add' => $permission]
        : ['Remove' => $permission];

    // Update image.
    $this->ec2Service->setCloudContext($entity->getCloudContext());

    if (!method_exists($this->ec2Service, 'modifyImageAttribute')) {
      return;
    }

    // Update image description.
    $this->ec2Service->modifyImageAttribute([
      'ImageId' => $entity->getImageId(),
      'Description' => ['Value' => $entity->getDescription()],
    ]);

    // These fields (Description , LaunchPermission , or ProductCode) cannot
    // be specified at the same time.
    // See also:
    // https://docs.aws.amazon.com/cli/latest/reference/ec2/modify-image-attribute.html
    // Update image launch permission.
    $this->ec2Service->modifyImageAttribute([
      'ImageId' => $entity->getImageId(),
      'LaunchPermission' => $launch_permission,
    ]);

    $new_account_ids = [];
    foreach ($entity->getLaunchPermissionAccountIds() as $account_id) {
      if (empty($account_id->value)) {
        continue;
      }
      $new_account_ids[] = $account_id->value;
    }

    $old_account_ids = [];
    foreach ($original_entity->getLaunchPermissionAccountIds() as $account_id) {
      if (empty($account_id->value)) {
        continue;
      }
      $old_account_ids[] = $account_id->value;
    }

    $account_ids_to_remove = array_diff($old_account_ids, $new_account_ids);
    $account_ids_to_add = array_diff($new_account_ids, $old_account_ids);

    if (!empty($account_ids_to_remove)) {
      $this->ec2Service->modifyImageAttribute([
        'ImageId' => $entity->getImageId(),
        'LaunchPermission' => [
          'Remove' => array_map(function ($item) {
            return ['UserId' => $item];
          }, $account_ids_to_remove),
        ],
      ]);
    }

    if (!empty($account_ids_to_add)) {
      $this->ec2Service->modifyImageAttribute([
        'ImageId' => $entity->getImageId(),
        'LaunchPermission' => [
          'Add' => array_map(function ($item) {
            return ['UserId' => $item];
          }, $account_ids_to_add),
        ],
      ]);
    }

    $this->clearCacheValues($entity->getCacheTags());
    $this->dispatchSaveEvent($entity);
  }

  /**
   * {@inheritdoc}
   */
  public function editOpenStackImage(ImageInterface $entity, array &$form, FormStateInterface $form_state): bool {
    try {
      $this->entity = $entity;
      $this->ec2Service = $this->openStackServiceFactory->get($entity->getCloudContext());
      $this->saveImage($form, $form_state);

      /**
       * The entity being used by this form.
       *
       * @var \Drupal\aws_cloud\Entity\Ec2\ImageInterface|\Drupal\aws_cloud\Entity\Ec2\Image
       */
      $original_entity = $entity;
      $visibility = !empty($original_entity->getVisibility()) ? 'public' : 'private';

      // Update Image of OpenStack REST API.
      if ($this->ec2Service instanceof OpenStackRestService) {
        // Make an API call to update image.
        $result = $this->ec2Service->updateImage([
          'ImageId' => $original_entity->getImageId(),
          'Name' => $original_entity->getName(),
          'Visibility' => $visibility,
        ]);

        $old_tags = $result['Tags'] ?? [];

        foreach ($old_tags ?: [] as $tag) {
          // Delete old tags.
          $this->ec2Service->deleteTags(
            [
              'ImageId'    => $original_entity->getImageId(),
              'Tag'        => $tag,
              'EntityType' => $original_entity->getEntityTypeId(),
            ]);
        }

        $this->updateNameAndCreatedByTags($original_entity, $original_entity->getImageId());
      }

      return TRUE;
    }
    catch (Ec2ServiceException $e) {
      $this->handleException($e);
      return FALSE;
    }
  }

  /**
   * Delete an Image.
   *
+15 −0
Changes for modules/cloud_service_providers/openstack/src/Service/OpenStackOperationsServiceInterface.php: 15 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -25,6 +25,21 @@ interface OpenStackOperationsServiceInterface {
   */
  public function createOpenStackImage(ImageInterface $entity, array &$form, FormStateInterface $form_state): bool;

  /**
   * Edit an OpenStack image.
   *
   * @param \Drupal\aws_cloud\Entity\Ec2\ImageInterface $entity
   *   The OpenStack image entity.
   * @param array $form
   *   Array of form object.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current form state.
   *
   * @return bool
   *   TRUE when the process succeeds.
   */
  public function editOpenStackImage(ImageInterface $entity, array &$form, FormStateInterface $form_state): bool;

  /**
   * Delete an OpenStack image.
   *