Skip to content
Snippets Groups Projects
Commit 916db1ab authored by Ryo Yamashita's avatar Ryo Yamashita Committed by Yas Naoi
Browse files

Issue #3275389 by Ryo Yamashita, yas, Xiaohua Guan: Add the function to create...

Issue #3275389 by Ryo Yamashita, yas, Xiaohua Guan: Add the function to create OpenStack Image in the SPA
parent eec3efad
Branches
Tags
4 merge requests!1759Issue #3356778: Release 5.1.1,!1679Issue #3349074: Fix the OpenStack Project create and edit form in SPA that "Member" cannot be saved due to a validation error,!1607Issue #3343582: Add the function to preview OpenStack stack in the SPA,!1032Issue #3284576: Release 5.0.0-alpha2
Showing
with 466 additions and 66 deletions
......@@ -3,6 +3,7 @@ import K8S_NAMESPACE_TEMPLATE from 'constant/form_template/k8s/namespace';
import K8S_OTHER_TEMPLATE from 'constant/form_template/k8s/other';
import K8S_POD_TEMPLATE from 'constant/form_template/k8s/pod';
import K8S_SCHEDULE_TEMPLATE from 'constant/form_template/k8s/schedule';
import OPENSTACK_IMAGE_TEMPLATE from 'constant/form_template/openstack/image';
import VMWARE_VM_TEMPLATE from 'constant/form_template/vmware/vm';
const ENTITY_FORM_LIST = [
......@@ -12,6 +13,7 @@ const ENTITY_FORM_LIST = [
...K8S_SCHEDULE_TEMPLATE,
// K8S_OTHER_TEMPLATE must be registered after the other K8S_XXX_TEMPLATE.
...K8S_OTHER_TEMPLATE,
...OPENSTACK_IMAGE_TEMPLATE,
...VMWARE_VM_TEMPLATE,
];
......
......@@ -27,7 +27,8 @@ const K8S_DEPLOYMENT_TEMPLATE: EntityFormTemplate[] = [
value: [
{ labelName: 'Use Cloud Orchestrator', name: 'cloud_orchestrator_scheduler' },
{ labelName: 'Use CronJob', name: 'cronjob_scheduler' }
], defaultValue: 'cloud_orchestrator_scheduler'
], defaultValue: 'cloud_orchestrator_scheduler',
orientation: 'vertical'
},
{
type: 'time', labelName: 'Start-up Time', hourName: 'startHour', minuteName: 'startMinute',
......
......@@ -37,7 +37,8 @@ const K8S_POD_TEMPLATE: EntityFormTemplate[] = [
value: [
{ labelName: 'Use Cloud Orchestrator', name: 'cloud_orchestrator_scheduler' },
{ labelName: 'Use CronJob', name: 'cronjob_scheduler' }
], defaultValue: 'cloud_orchestrator_scheduler'
], defaultValue: 'cloud_orchestrator_scheduler',
orientation: 'vertical'
},
{
type: 'time', labelName: 'Start-up Time',
......
import EntityFormTemplate from 'model/EntityFormTemplate';
const OPENSTACK_IMAGE_TEMPLATE: EntityFormTemplate[] = [
{
cloudServiceProvider: 'openstack',
entityName: 'image',
actionType: 'create',
entityRecords: [
{
type: 'panel',
panelName: 'Image',
keyValueRecords: [
{
type: 'default', labelName: 'Name', name: 'name', defaultValue: ''
},
{
type: 'radio', labelName: 'Visibility', name: 'visibility', defaultValue: '0',
value: [
{ labelName: 'Private', name: '0' },
{ labelName: 'Public', name: '1' },
], orientation: 'horizontal'
}
]
}
]
}
]
export default OPENSTACK_IMAGE_TEMPLATE;
......@@ -41,6 +41,7 @@ type EntityFormColumnInfo = {
type: 'radio',
value: { labelName: string, name: string }[],
defaultValue: string,
orientation: 'horizontal' | 'vertical'
};
type EntityFormColumn = (EntityFormColumnBase & EntityFormColumnInfo) | {
......
......@@ -123,6 +123,7 @@ const EntityFormBlock = ({ keyValueRecord, cloudContext, formData, setFormData }
value={value}
setValue={setValue}
valueList={keyValueRecord.value}
orientation={keyValueRecord.orientation}
required={!!keyValueRecord.required}
readOnly={!!keyValueRecord.readOnly} />;
default:
......
......@@ -5,36 +5,59 @@ import Form from 'bootstrap3-components/Form';
*
* @param
*/
const RadioSelectBlock = ({ label, value, setValue, valueList, required, readOnly }: {
const RadioSelectBlock = ({ label, value, setValue, valueList, orientation, required, readOnly }: {
label: string,
value: string,
setValue: (s: string) => void,
valueList: { labelName: string, name: string }[],
orientation: 'horizontal' | 'vertical',
required: boolean,
readOnly: boolean,
}) => {
return <Form.Group>
<Form.Group>
<Form.Label className={required ? 'form-required' : ''}>{label}</Form.Label>
</Form.Group>
<div>
{
valueList.map((valueRecord) => {
return <div className="radio" key={valueRecord.name}>
<Form.Label className="option">
<input className="form-radio" type="radio"
checked={value === valueRecord.name}
onChange={() => {
setValue(valueRecord.name);
}} readOnly={readOnly} />
{valueRecord.labelName}
</Form.Label>
</div>;
})
}
</div>
</Form.Group>;
if (orientation === 'vertical') {
return <Form.Group>
<Form.Group>
<Form.Label className={required ? 'form-required' : ''}>{label}</Form.Label>
</Form.Group>
<div>
{
valueList.map((valueRecord) => {
return <div className="radio" key={valueRecord.name}>
<Form.Label className="option">
<input className="form-radio" type="radio"
checked={value === valueRecord.name}
onChange={() => {
setValue(valueRecord.name);
}} readOnly={readOnly} />
{valueRecord.labelName}
</Form.Label>
</div>;
})
}
</div>
</Form.Group>;
} else {
return <div className="container-inline">
<b className={required ? 'form-required' : ''}>{`${label}: `}</b>
<div>
{
valueList.map((valueRecord) => {
return <div className="radio" style={{marginRight: 5}} key={valueRecord.name}>
<Form.Label className="option">
<input className="form-radio" type="radio"
checked={value === valueRecord.name}
onChange={() => {
setValue(valueRecord.name);
}} readOnly={readOnly} />
{valueRecord.labelName}
</Form.Label>
</div>;
})
}
</div>
</div>;
}
}
export default RadioSelectBlock;
......@@ -465,3 +465,18 @@ entity.openstack_entity.count_all:
_custom_access: '\Drupal\cloud\Controller\CloudConfigController::access'
options:
perm: 'list {entity_type_id}'
entity.openstack_image.create:
path: '/cloud_dashboard/openstack/{cloud_context}/openstack_image/create'
defaults:
_controller: '\Drupal\openstack\Controller\ApiController::operateEntity'
entity_type_id: openstack_image
entity_id: ''
command: create
methods: [POST]
requirements:
# Use custom access that will check for cloud_context and the desired permission.
# Desired permission is passed as an option in the "perm" variable
_custom_access: '\Drupal\cloud\Controller\CloudConfigController::access'
options:
perm: 'add openstack image'
......@@ -20,4 +20,4 @@ services:
openstack.ec2_operations:
class: Drupal\openstack\Service\Ec2\OpenStackOperationsService
arguments: ['@entity_type.manager', '@openstack.ec2']
arguments: ['@entity_type.manager', '@openstack.ec2', '@plugin.manager.cloud_config_plugin', '@cloud']
......@@ -11,12 +11,14 @@ use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Messenger\Messenger;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\openstack\Service\Ec2\OpenStackOperationsService;
use Drupal\openstack\Service\OpenStackServiceFactoryInterface;
use Drupal\views\Views;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
......@@ -465,4 +467,67 @@ class ApiController extends ControllerBase implements ApiControllerInterface {
return new JsonResponse(['count' => $count]);
}
/**
* {@inheritdoc}
*/
public function operateEntity(Request $request, string $cloud_context, string $entity_type_id, string $entity_id, string $command): JsonResponse {
$entity = $command !== 'create'
? $this->entityTypeManager
->getStorage($entity_type_id)
->load($entity_id)
: $this->entityTypeManager
->getStorage($entity_type_id)
->create([
'cloud_context' => $cloud_context,
]);
if (empty($entity)) {
return new JsonResponse([
'result' => 'NG',
'reason' => 'The instance has already been deleted.',
], 404);
}
$method_name = '';
$parameter = [];
switch ($command) {
case 'create':
switch ($entity_type_id) {
case 'openstack_image':
$method_name = 'createImage';
$parameter = [
'name' => $request->get('name', ''),
];
$entity->setName($request->get('name', ''));
$entity->setVisibility(!empty($request->get('visibility', '')) ? 'public' : 'private');
break;
}
break;
}
$result = FALSE;
if (!empty($method_name)) {
$service = new OpenStackOperationsService(
$this->entityTypeManager,
\Drupal::service('aws_cloud.ec2'),
$this->cloudConfigPluginManager,
$this->cloudService
);
$ec2Service = $this->openStackServiceFactory->get($cloud_context);
$service->setEc2Service($ec2Service);
if (method_exists($service, $method_name)) {
$result = $service->$method_name($entity, $parameter);
}
}
return !empty($result)
? new JsonResponse([
'result' => 'OK',
], 200)
: new JsonResponse([
'result' => 'NG',
'reason' => 'Internal Server Error',
], 500);
}
}
......@@ -4,6 +4,7 @@ namespace Drupal\openstack\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
/**
* Api Controller interface.
......@@ -119,4 +120,23 @@ interface ApiControllerInterface {
*/
public function getEntityCount(string $cloud_context, string $entity_type_id): JsonResponse;
/**
* Operate OpenStack entity.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* HTTP request.
* @param string $cloud_context
* The cloud context.
* @param string $entity_type_id
* The entity type ID.
* @param string $entity_id
* The entity ID.
* @param string $command
* Operation command.
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
* The JSON response.
*/
public function operateEntity(Request $request, string $cloud_context, string $entity_type_id, string $entity_id, string $command): JsonResponse;
}
......@@ -2,10 +2,10 @@
namespace Drupal\openstack\Form;
use Drupal\openstack\Service\Rest\OpenStackService as OpenStackRestService;
use Drupal\aws_cloud\Form\Ec2\ImageCreateForm;
use Drupal\aws_cloud\Service\Ec2\Ec2ServiceInterface;
use Drupal\cloud\Plugin\cloud\config\CloudConfigPluginManagerInterface;
use Drupal\cloud\Service\CloudServiceInterface;
use Drupal\cloud\Service\EntityLinkRendererInterface;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Cache\CacheBackendInterface;
......@@ -19,8 +19,9 @@ use Drupal\Core\Plugin\CachedDiscoveryClearerInterface;
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\OpenStackOperationsServiceInterface;
use Drupal\openstack\Service\OpenStackServiceFactoryInterface;
use Drupal\openstack\Service\Rest\OpenStackService as OpenStackRestService;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
......@@ -37,6 +38,13 @@ class OpenStackImageCreateForm extends ImageCreateForm {
*/
protected $openStackServiceFactory;
/**
* The OpenStack operation service.
*
* @var \Drupal\openstack\Service\Ec2\OpenStackOperationsServiceInterface
*/
private $openStackOperationsService;
/**
* OpenStackImageCreateForm constructor.
*
......@@ -44,6 +52,8 @@ 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\OpenStackOperationsServiceInterface $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
......@@ -73,22 +83,25 @@ class OpenStackImageCreateForm extends ImageCreateForm {
* @param \Drupal\cloud\Service\CloudServiceInterface $cloud_service
* The Cloud service.
*/
public function __construct(OpenStackServiceFactoryInterface $openstack_service_factory,
Ec2ServiceInterface $ec2_service,
EntityRepositoryInterface $entity_repository,
EntityTypeBundleInfoInterface $entity_type_bundle_info,
TimeInterface $time,
Messenger $messenger,
EntityLinkRendererInterface $entity_link_renderer,
EntityTypeManager $entity_type_manager,
CacheBackendInterface $cacheRender,
CachedDiscoveryClearerInterface $plugin_cache_clearer,
CloudConfigPluginManagerInterface $cloud_config_plugin_manager,
AccountInterface $current_user,
RouteMatchInterface $route_match,
DateFormatterInterface $date_formatter,
Renderer $renderer,
CloudServiceInterface $cloud_service) {
public function __construct(
OpenStackServiceFactoryInterface $openstack_service_factory,
Ec2ServiceInterface $ec2_service,
OpenStackOperationsServiceInterface $openstack_operations_service,
EntityRepositoryInterface $entity_repository,
EntityTypeBundleInfoInterface $entity_type_bundle_info,
TimeInterface $time,
Messenger $messenger,
EntityLinkRendererInterface $entity_link_renderer,
EntityTypeManager $entity_type_manager,
CacheBackendInterface $cacheRender,
CachedDiscoveryClearerInterface $plugin_cache_clearer,
CloudConfigPluginManagerInterface $cloud_config_plugin_manager,
AccountInterface $current_user,
RouteMatchInterface $route_match,
DateFormatterInterface $date_formatter,
Renderer $renderer,
CloudServiceInterface $cloud_service
) {
parent::__construct(
$ec2_service,
......@@ -109,6 +122,7 @@ class OpenStackImageCreateForm extends ImageCreateForm {
);
$this->openStackServiceFactory = $openstack_service_factory;
$this->openStackOperationsService = $openstack_operations_service;
}
/**
......@@ -124,6 +138,7 @@ 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'),
......@@ -195,18 +210,17 @@ class OpenStackImageCreateForm extends ImageCreateForm {
* The current form state.
*/
public function save(array $form, FormStateInterface $form_state): void {
$entity = $this->entity;
// Switch OpenStack EC2 or REST service based on $entity->getCloudContext().
$this->ec2Service = $this->openStackServiceFactory->get($entity->getCloudContext());
$this->trimTextfields($form, $form_state);
parent::save($form, $form_state);
if (!empty($result['SendToWorker'])) {
return;
}
if ($this->ec2Service instanceof OpenStackRestService) {
$this->updateNameAndCreatedByTags($entity, $entity->getImageId());
$entity = $this->entity;
$cloudContext = $entity->getCloudContext();
$ec2Service = $this->openStackServiceFactory->get($cloudContext);
$this->openStackOperationsService->setEc2Service($ec2Service);
if ($this->openStackOperationsService->createImage($this->entity, [
'name' => $form_state->getValue('name'),
])) {
$form_state->setRedirect("view.{$entity->getEntityTypeId()}.list", ['cloud_context' => $entity->getCloudContext()]);
}
}
......
......@@ -2,32 +2,208 @@
namespace Drupal\openstack\Service\Ec2;
use Drupal\aws_cloud\Entity\Ec2\ImageInterface;
use Drupal\aws_cloud\Service\Ec2\Ec2OperationsService;
use Drupal\aws_cloud\Service\Ec2\Ec2ServiceInterface;
use Drupal\cloud\Plugin\cloud\config\CloudConfigPluginManagerInterface;
use Drupal\cloud\Service\CloudServiceInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\openstack\Service\OpenStackServiceInterface;
use Drupal\openstack\Service\Rest\OpenStackService as OpenStackRestService;
/**
* Entity update methods for REST API processing.
*/
class OpenStackOperationsService extends Ec2OperationsService {
class OpenStackOperationsService extends Ec2OperationsService implements OpenStackOperationsServiceInterface {
/**
* OpenStackOperationsService constructor.
* The cloud service provider plugin manager (CloudConfigPluginManager).
*
* @var \Drupal\cloud\Plugin\cloud\config\CloudConfigPluginManagerInterface
*/
protected $cloudConfigPluginManager;
/**
* The Cloud service.
*
* @var \Drupal\cloud\Service\CloudServiceInterface
*/
protected $cloudService;
/**
* The entity.
*
* @var \Drupal\Core\Entity\EntityInterface
*/
protected $entity;
/**
* Ec2OperationsService constructor.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* An entity type manager instance.
* @param \Drupal\openstack\Service\OpenStackServiceInterface $openstack_service
* Object for interfacing with OpenStack Service.
* @param \Drupal\aws_cloud\Service\Ec2\Ec2ServiceInterface $ec2_service
* The AWS Cloud EC2 service.
* @param \Drupal\cloud\Plugin\cloud\config\CloudConfigPluginManagerInterface $cloud_config_plugin_manager
* The cloud service provider plugin manager (CloudConfigPluginManager).
* @param \Drupal\cloud\Service\CloudServiceInterface $cloud_service
* The Cloud service.
*/
public function __construct(
EntityTypeManagerInterface $entity_type_manager,
Ec2ServiceInterface $ec2_service,
CloudConfigPluginManagerInterface $cloud_config_plugin_manager,
CloudServiceInterface $cloud_service
) {
parent::__construct($entity_type_manager, $ec2_service);
$this->cloudConfigPluginManager = $cloud_config_plugin_manager;
$this->cloudService = $cloud_service;
}
/**
* {@inheritdoc}
*/
public function setEc2Service(mixed $ec2_service): void {
$this->ec2Service = $ec2_service;
}
/**
* {@inheritdoc}
*/
public function setEntity(EntityInterface $entity): void {
$this->entity = $entity;
}
/**
* Check if current cloud config is remote or not.
*
* @return bool
* True if the current cloud config is remote.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager,
OpenStackServiceInterface $openstack_service) {
private function isCloudConfigRemote(): bool {
$this->cloudConfigPluginManager->setCloudContext($this->entity->getCloudContext());
$cloud_config = $this->cloudConfigPluginManager->loadConfigEntity();
return $cloud_config->isRemote();
}
/**
* Set the tags for the entity.
*
* @param string $resource_id
* The resource ID. For example, instance_id, volume_id.
* @param array $tag_map
* The map of tags.
* @param string $entity_type
* The entity type.
*/
private function setTagsInAws(string $resource_id, array $tag_map, string $entity_type = ''): void {
$this->ec2Service->setCloudContext($this->entity->getCloudContext());
$tags = [];
foreach ($tag_map ?: [] as $key => $value) {
$tags[] = [
'Key' => $key,
'Value' => $value,
];
}
if (!method_exists($this->ec2Service, 'createTags')) {
return;
}
// Create Tags with different parameters for AWS and OpenStack.
if (preg_match('[^aws_cloud]', $this->entity->getEntityTypeId()) === 1) {
$this->ec2Service->createTags([
'Resources' => [$resource_id],
'Tags' => $tags,
]);
}
else {
$this->ec2Service->createTags([
'Resources' => [$resource_id],
'Tags' => $tags,
'EntityType' => $entity_type,
]);
}
}
/**
* {@inheritdoc}
*/
public function updateNameAndCreatedByTags(EntityInterface $entity, string $aws_cloud_resource_id): void {
$cloud_context = $entity->getCloudContext();
if ($this->isCloudConfigRemote()) {
// Get original cloud context.
if (($pos = strrpos($cloud_context, '_')) !== FALSE) {
$cloud_context = substr($cloud_context, 0, $pos);
}
}
$bundle = strpos($entity->bundle(), 'aws_cloud') !== FALSE
? 'aws_cloud' : 'openstack';
$uid_key_name = $this->cloudService->getTagCreatedByUid($bundle, $cloud_context);
$this->setTagsInAws(
$aws_cloud_resource_id,
[
$entity->getEntityTypeId() . '_' . $uid_key_name => !empty($entity->getOwner())
? $entity->getOwner()->id() : 0,
'Name' => $entity->getName(),
],
$entity->getEntityTypeId()
);
}
/**
* {@inheritdoc}
*/
public function createImage(ImageInterface $entity, array $params): bool {
$this->setEntity($entity);
$result = $this->ec2Service->createImage([
'InstanceId' => $entity->getInstanceId(),
'Name' => $entity->getName(),
'Description' => $entity->getDescription(),
'Visibility' => $entity->getVisibility(),
]);
if (!empty($result['SendToWorker'])) {
$this->processOperationStatus($entity, 'created remotely');
return TRUE;
}
$this->cloudConfigPluginManager->setCloudContext($entity->getCloudContext());
$account_id = $this->cloudConfigPluginManager->loadConfigEntity()->get('field_account_id')->value;
if (
!empty($entity) && !empty($result['ImageId'])
&& ($entity->setName($params['name']))
&& ($entity->set('ami_name', $params['name']))
&& ($entity->setImageId($result['ImageId']))
&& ($entity->set('account_id', $account_id))
&& ($entity->save())
) {
$this->updateNameAndCreatedByTags($entity, $entity->getImageId());
$this->processOperationStatus($entity, 'created');
$this->dispatchSaveEvent($entity);
if ($this->ec2Service instanceof OpenStackRestService) {
$this->updateNameAndCreatedByTags($entity, $entity->getImageId());
}
return TRUE;
}
else {
$this->processOperationErrorStatus($entity, 'created');
// The parent constructor takes care of $this->messenger object.
parent::__construct($entity_type_manager, \Drupal::service('aws_cloud.ec2'));
if ($this->ec2Service instanceof OpenStackRestService) {
$this->updateNameAndCreatedByTags($entity, $entity->getImageId());
}
// Setup the entity type manager for querying entities.
$this->entityTypeManager = $entity_type_manager;
$this->ec2Service = $openstack_service;
return FALSE;
}
}
}
<?php
namespace Drupal\openstack\Service\Ec2;
use Drupal\aws_cloud\Entity\Ec2\ImageInterface;
use Drupal\Core\Entity\EntityInterface;
/**
* The Interface for K8sOperationsService.
*/
interface OpenStackOperationsServiceInterface {
/**
* Set the EC2 Service.
*
* @param \Drupal\aws_cloud\Service\Ec2\Ec2ServiceInterface|\Drupal\openstack\Service\OpenStackServiceInterface $ec2_service
* The EC2 Service.
*/
public function setEc2Service(mixed $ec2_service): void;
/**
* Set the EC2 Service.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity.
*/
public function setEntity(EntityInterface $entity): void;
/**
* Update name and created_by tags.
*
* @param Drupal\Core\Entity\EntityInterface $entity
* The entity.
* @param string $aws_cloud_resource_id
* The AWS Cloud resource ID.
*/
public function updateNameAndCreatedByTags(EntityInterface $entity, string $aws_cloud_resource_id): void;
/**
* Create AWS OpenStack image.
*
* @param \Drupal\aws_cloud\Entity\Ec2\ImageInterface $entity
* The OpenStack image entity.
* @param array $params
* Optional parameters array.
*
* @return bool
* TRUE when the process succeeds.
*/
public function createImage(ImageInterface $entity, array $params): bool;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment