diff --git a/cloud.install b/cloud.install
index 43238d5..5876d55 100644
--- a/cloud.install
+++ b/cloud.install
@@ -5,6 +5,7 @@
  * Install and updates for aws_cloud.
  */
 
+use Drupal\Component\Utility\DeprecationHelper;
 use Drupal\Component\Serialization\Yaml;
 use Drupal\Core\Config\FileStorage;
 use Drupal\Core\Field\BaseFieldDefinition;
@@ -826,7 +827,7 @@ function cloud_update_8147(): void {
         unset($new_field_storage['dependencies']['enforced']);
 
         $new_field_storage = FieldStorageConfig::create($new_field_storage);
-        $new_field_storage->original = $new_field_storage;
+        DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => $new_field_storage->setOriginal($new_field_storage), fn() => $new_field_storage->original = $new_field_storage);
         $new_field_storage->enforceIsNew(FALSE);
         $new_field_storage->save();
       }
@@ -1061,7 +1062,7 @@ function cloud_update_8153() {
         unset($new_field_storage['dependencies']['enforced']);
 
         $new_field_storage = FieldStorageConfig::create($new_field_storage);
-        $new_field_storage->original = $new_field_storage;
+        DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => $new_field_storage->setOriginal($new_field_storage), fn() => $new_field_storage->original = $new_field_storage);
         $new_field_storage->enforceIsNew(FALSE);
         $new_field_storage->save();
       }
diff --git a/cloud.module b/cloud.module
index 13a4538..38bed2a 100644
--- a/cloud.module
+++ b/cloud.module
@@ -7,118 +7,57 @@
  * Provides common functionality for cloud management.
  */
 
-use Drupal\Component\Utility\Html;
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\cloud\Hook\CloudHooks;
 use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Locale\CountryManager;
 use Drupal\Core\Routing\RouteMatchInterface;
-use Drupal\Core\Url;
 use Drupal\cloud\Entity\CloudConfig;
 use Drupal\cloud\Entity\CloudLaunchTemplateInterface;
 use Drupal\field\Entity\FieldStorageConfig;
 use Drupal\views\ViewExecutable;
-use Drupal\views\Views;
 
 /**
  * Implements hook_cron().
  */
+#[LegacyHook]
 function cloud_cron() {
-  $config = \Drupal::config('cloud.settings');
-  if ($config->get('cloud_enable_queue_cleanup') === TRUE) {
-    // Clean out batch items older than 1 day.
-    \Drupal::database()
-      ->delete('queue')
-      ->condition('created', time() - 86400, '<')
-      ->condition('name', 'drupal_batch:%', 'LIKE')
-      ->execute();
-  }
+  \Drupal::service(CloudHooks::class)->cron();
 }
 
 /**
  * Implements hook_help().
  */
+#[LegacyHook]
 function cloud_help($route_name, RouteMatchInterface $route_match): string {
-  switch ($route_name) {
-    case 'help.page.cloud':
-      $output = '<h3>' . t('About') . '</h3>';
-      $output .= '<ul>';
-      $output .= '<li>' . \Drupal::service('cloud')->getVersion() . '</li>';
-      $output .= '<li>' . t('The cloud module creates a user interface for users to manage clouds. Users can "Create instances", "Describe instances", and etc.') . '</li>';
-      $output .= '</ul>';
-      $output .= '<p>' . t('For more information, see the <a href=":cloud_documentation">online documentation for the Cloud module</a>.', [':cloud_documentation' => 'https://www.drupal.org/docs/8/modules/cloud']) . '</p>';
-      return $output;
-
-    case 'help.page.cloud_launch_template':
-      $output = '<h3>' . t('About') . '</h3>';
-      $output .= '<p>' . t('The launch template (a.k.a. cloud server template) module creates a user interface for users to manage clouds. Users can create launch templates.') . '</p>';
-      $output .= '<p>' . t('For more information, see the <a href=":cloud_documentation">online documentation for the Cloud module</a>.', [':cloud_documentation' => 'https://www.drupal.org/docs/8/modules/cloud']) . '</p>';
-      return $output;
-
-    default:
-      return '';
-  }
+  return \Drupal::service(CloudHooks::class)->help($route_name, $route_match);
 }
 
 /**
  * Implements hook_theme().
  */
+#[LegacyHook]
 function cloud_theme(): array {
-  $theme = [];
-  $theme['cloud_config'] = [
-    'render element' => 'elements',
-    'file' => 'cloud_config.page.inc',
-    'template' => 'cloud_config',
-  ];
-  $theme['cloud_config_content_add_list'] = [
-    'render element' => 'content',
-    'variables' => ['content' => NULL],
-    'file' => 'cloud_config.page.inc',
-  ];
-  $theme['cloud_launch_template'] = [
-    'render element' => 'elements',
-    'file' => 'cloud_launch_template.page.inc',
-    'template' => 'cloud_launch_template',
-  ];
-  $theme['cloud_launch_template_content_add_list'] = [
-    'render element' => 'content',
-    'variables' => ['content' => NULL],
-    'file' => 'cloud_launch_template.page.inc',
-  ];
-  return $theme;
+  return \Drupal::service(CloudHooks::class)->theme();
 }
 
 /**
  * Implements hook_theme_suggestions_HOOK().
  */
+#[LegacyHook]
 function cloud_theme_suggestions_cloud_config(array $variables): array {
-  $suggestions = [];
-  $entity = $variables['elements']['#cloud_config'];
-  $sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_');
-
-  $suggestions[] = 'cloud_config__' . $sanitized_view_mode;
-  $suggestions[] = 'cloud_config__' . $entity->bundle();
-  $suggestions[] = 'cloud_config__' . $entity->bundle() . '__' . $sanitized_view_mode;
-  $suggestions[] = 'cloud_config__' . $entity->id();
-  $suggestions[] = 'cloud_config__' . $entity->id() . '__' . $sanitized_view_mode;
-  return $suggestions;
+  return \Drupal::service(CloudHooks::class)->themeSuggestionsCloudConfig($variables);
 }
 
 /**
  * Implements hook_theme_suggestions_HOOK().
  */
+#[LegacyHook]
 function cloud_theme_suggestions_cloud_launch_template(array $variables): array {
-  $suggestions = [];
-  $entity = $variables['elements']['#cloud_launch_template'];
-  $sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_');
-
-  $suggestions[] = 'cloud_server_template__' . $sanitized_view_mode;
-  $suggestions[] = 'cloud_server_template__' . $entity->bundle();
-  $suggestions[] = 'cloud_server_template__' . $entity->bundle() . '__' . $sanitized_view_mode;
-  $suggestions[] = 'cloud_server_template__' . $entity->id();
-  $suggestions[] = 'cloud_server_template__' . $entity->id() . '__' . $sanitized_view_mode;
-  return $suggestions;
+  return \Drupal::service(CloudHooks::class)->themeSuggestionsCloudLaunchTemplate($variables);
 }
 
 /**
@@ -131,39 +70,9 @@ function cloud_theme_suggestions_cloud_launch_template(array $variables): array
  * and call hasPermissions() with the entity's cloud_context.
  * Unset the entity if the user does not have permissions.
  */
+#[LegacyHook]
 function cloud_views_pre_render(ViewExecutable $view): void {
-  $account = \Drupal::currentUser();
-  if ($view->id() === 'cloud_config' || $view->id() === 'cloud_launch_template') {
-    foreach ($view->result ?: [] as $key => $result) {
-      /** @var \Drupal\cloud\Entity\CloudConfigInterface $cloud */
-      $cloud = $result->_entity;
-      if (!$account->hasPermission('view ' . $cloud->getCloudContext())
-        && !$account->hasPermission('view all cloud service providers')) {
-        unset($view->result[$key]);
-      }
-    }
-  }
-
-  if (!empty($view->empty)) {
-    return;
-  }
-  $view->style_plugin->options['empty_table'] = TRUE;
-  $options = [
-    'id' => 'area',
-    'table' => 'views',
-    'field' => 'area',
-    'label' => '',
-    'relationship' => 'none',
-    'group_type' => 'group',
-    'empty' => TRUE,
-    'content' => [
-      'value' => t('No items.'),
-      'format' => filter_default_format(),
-    ],
-  ];
-  $handler = Views::handlerManager('area')->getHandler($options);
-  $handler->init($view, $view->display_handler, $options);
-  $view->empty['area'] = $handler;
+  \Drupal::service(CloudHooks::class)->viewsPreRender($view);
 }
 
 /**
@@ -171,24 +80,9 @@ function cloud_views_pre_render(ViewExecutable $view): void {
  *
  * Hook for form views_form_cloud_config_*.
  */
+#[LegacyHook]
 function cloud_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  if (strpos($form_id, 'views_form_cloud_config_') === 0) {
-    $form['#submit'][] = 'cloud_config_bulk_form_submit';
-  }
-
-  if (strpos($form_id, 'cloud_config_') === 0
-    && (strpos($form_id, 'add_form') !== FALSE ||
-      strpos($form_id, 'edit_form') !== FALSE)
-    && \Drupal::service('cloud')->isGeocoderAvailable() === TRUE) {
-
-    $path = \Drupal::service('router.route_provider')->getRouteByName('entity.cloud_config.geocoder')->getPath();
-    $path = str_replace('{country}', 'country', $path);
-    $path = str_replace('{city}', 'city', $path);
-    $url = Url::fromUri('internal:' . $path);
-    $form['#attached']['library'] = 'cloud/cloud_geocoder';
-    $form['#attached']['drupalSettings']['cloud']['geocoder_url'] = $url->toString();
-
-  }
+  \Drupal::service(CloudHooks::class)->formAlter($form, $form_state, $form_id);
 }
 
 /**
@@ -238,108 +132,50 @@ function cloud_views_bulk_form_submit(array $form, FormStateInterface $form_stat
 /**
  * Implements hook_entity_view_alter().
  */
+#[LegacyHook]
 function cloud_entity_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display): void {
-  $build['#attached']['library'][] = 'cloud/cloud_view_builder';
+  \Drupal::service(CloudHooks::class)->entityViewAlter($build, $entity, $display);
 }
 
 /**
  * Implements hook_preprocess_views_view().
  */
+#[LegacyHook]
 function cloud_preprocess_views_view(array &$variables): void {
-  $view = $variables['view'];
-  if ($view->ajaxEnabled() && empty($view->is_attachment) && empty($view->live_preview)) {
-    // @todo https://www.drupal.org/project/drupal/issues/2866386
-    $view->element['#attached']['drupalSettings']['views']['ajaxViews']['views_dom_id:' . $view->dom_id]['view_path']
-      = \Drupal::routeMatch()->getRouteName() === 'views.ajax'
-        ? \Drupal::service('path.current')->getPath()
-        : Html::escape(Url::fromRoute('<current>')->toString());
-  }
+  \Drupal::service(CloudHooks::class)->preprocessViewsView($variables);
 }
 
 /**
  * Implements hook_library_info_alter().
  */
+#[LegacyHook]
 function cloud_library_info_alter(&$libraries, $extension): void {
 
-  if ($extension === 'cloud') {
-    $config = \Drupal::configFactory()->getEditable('cloud.settings');
-    $source = $config->get('cloud_use_default_urls');
-
-    if (empty($source)) {
-      $libraries['d3']['js'] = [];
-      $libraries['d3-horizon-chart']['js'] = [];
-      $libraries['c3']['js'] = [];
-      $libraries['c3']['css']['theme'] = [];
-      $libraries['chartjs']['js'] = [];
-      $libraries['select2']['js'] = [];
-      $libraries['select2']['css']['theme'] = [];
-
-      $libraries['d3']['js'][trim($config->get('cloud_custom_d3_js_url') ?: '')] = [];
-      $libraries['d3-horizon-chart']['js'][trim($config->get('cloud_custom_d3_horizon_chart_js_url') ?: '')] = [];
-      $libraries['c3']['js'][trim($config->get('cloud_custom_c3_js_url') ?: '')] = [];
-      $libraries['c3']['css']['theme'][trim($config->get('cloud_custom_c3_css_url') ?: '')] = [];
-      $libraries['chartjs']['js'][trim($config->get('cloud_custom_chart_js_url') ?: '')] = [];
-      $libraries['select2']['js'][trim($config->get('cloud_custom_select2_js_url') ?: '')] = [];
-      $libraries['select2']['css']['theme'][trim($config->get('cloud_custom_select2_css_url') ?: '')] = [];
-      $libraries['datatables']['js'][trim($config->get('cloud_custom_datatables_js_url') ?: '')] = [];
-    }
-  }
+  \Drupal::service(CloudHooks::class)->libraryInfoAlter($libraries, $extension);
 }
 
 /**
  * Implements hook_preprocess_page().
  */
+#[LegacyHook]
 function cloud_preprocess_page(array &$variables): void {
-  $route = \Drupal::routeMatch()->getRouteObject();
-  if ($route) {
-    $view_id = $route->getDefault('view_id');
-    if ($view_id === 'cloud_config') {
-      $block_manager = \Drupal::service('plugin.manager.block');
-      $config = ['not_display_cloud_config_page' => TRUE];
-      $plugin_block = $block_manager->createInstance('cloud_config_location', $config);
-      $render = $plugin_block->build();
-      $variables['page']['content'] = [$render, $variables['page']['content']];
-    }
-  }
+  \Drupal::service(CloudHooks::class)->preprocessPage($variables);
 }
 
 /**
  * Implements hook_field_widget_single_element_form_alter().
  */
+#[LegacyHook]
 function cloud_field_widget_single_element_form_alter(&$element, FormStateInterface $form_state, array $context): void {
-  // The new field widget hook hook_field_widget_complete_form_alter was
-  // introduced to make it possible to alter the whole widget form after it was
-  // processed.
-  // hook_field_widget_form_alter was marked as deprecated and will be replaced
-  // by hook_field_widget_single_element_form_alter.  Also,
-  // hook_field_widget_multivalue_form_alter were marked as deprecated and will
-  // be removed in Drupal 10.
-  // Therefore, we replaced hook_field_widget_multivalue_form_alter to
-  // hook_field_widget_single_element_form_alter for Drupal 9.2.
-  $field_definition = $context['items']->getFieldDefinition();
-  $settings = $field_definition->getSettings();
-  if ($field_definition->getName() === 'field_location_latitude'
-    || $field_definition->getName() === 'field_location_longitude') {
-    // @todo Prevents validation of decimal numbers.
-    // @see https://www.drupal.org/node/2230909
-    $element['value']['#step'] = 'any';
-    $element['value']['#scale'] = $settings['scale'];
-  }
+  \Drupal::service(CloudHooks::class)->fieldWidgetSingleElementFormAlter($element, $form_state, $context);
 }
 
 /**
  * Implements hook_preprocess_input__number().
  */
+#[LegacyHook]
 function cloud_preprocess_input__number(array &$variables): void {
-  $element = &$variables['element'];
-  if (str_contains($element['#name'], 'field_location_latitude')
-    || str_contains($element['#name'], 'field_location_longitude')
-    || str_contains($element['#name'], 'aws_cloud_region_locations')) {
-    // @todo Enable the validation only on the browser for step settings.
-    // @see https://www.drupal.org/node/2230909
-    $scale = $element['#scale'];
-    $variables['attributes']['step'] = 0.1 ** $scale;
-  }
+  \Drupal::service(CloudHooks::class)->preprocessInputNumber($variables);
 }
 
 /**
@@ -364,15 +200,9 @@ function cloud_location_country_allowed_values_function(?FieldStorageConfig $def
 /**
  * Implements hook_modules_installed().
  */
+#[LegacyHook]
 function cloud_modules_installed($modules): void {
-  if ((!in_array('cloud', $modules) && !in_array('geocoder', $modules))
-    || (in_array('cloud', $modules) && !Drupal::moduleHandler()->moduleExists('geocoder'))) {
-    return;
-  }
-
-  // Initialize the geocoder provider.
-  $cloud_service = Drupal::getContainer()->get('cloud');
-  $cloud_service->initGeocoder();
+  \Drupal::service(CloudHooks::class)->modulesInstalled($modules);
 }
 
 /**
@@ -395,33 +225,9 @@ function cloud_get_views_items_options(): array {
 /**
  * Implements hook_preprocess_HOOK().
  */
+#[LegacyHook]
 function cloud_preprocess_menu(&$variables): void {
-  // Remove cloud service provider menus without sub-menu.
-  if (!empty($variables['menu_name']) && $variables['menu_name'] === 'main') {
-    $variables['#cache']['max-age'] = 0;
-
-    if (empty($variables['items'])) {
-      return;
-    }
-
-    foreach (['cloud.service_providers.menu', 'cloud.design.menu'] as $menu) {
-      if (empty($variables['items'][$menu])) {
-        continue;
-      }
-
-      foreach (($parent =& $variables['items'][$menu]['below']) as $key => $item) {
-        // Skip the menu "add cloud service provider".
-        if ($key === 'cloud.menu.cloud_links:cloud_service_provider.local_tasks') {
-          continue;
-        }
-
-        // If there is no sub-menu, remove the parent menu.
-        if (empty($item['below'])) {
-          unset($parent[$key]);
-        }
-      }
-    }
-  }
+  \Drupal::service(CloudHooks::class)->preprocessMenu($variables);
 }
 
 /**
@@ -465,76 +271,25 @@ function cloud_launch_template_workflow_status_allowed_values_function(FieldStor
 /**
  * Implements hook_mail().
  */
+#[LegacyHook]
 function cloud_mail($key, array &$message, array $params): void {
-  // This hook is called from MailManagerInterface->mail(). Note that
-  // hook_mail(),
-  // unlike hook_mail_alter(), is only called on the $module argument to
-  // MailManagerInterface->mail(), not all modules.
-  switch ($key) {
-    case 'notify_entity_admins_users':
-      $message['subject'] = $params['subject'];
-      $message['body'][] = $params['message'];
-      break;
-  }
+  \Drupal::service(CloudHooks::class)->mail($key, $message, $params);
 }
 
 /**
  * Implements hook_cloud_config_predelete().
  */
+#[LegacyHook]
 function cloud_cloud_config_predelete(CloudConfig $cloud_config): void {
-  // Delete all cloud project entities.
-  $storage = \Drupal::entityTypeManager()->getStorage('cloud_project');
-  $entities = $storage->loadByProperties([
-    'cloud_context' => $cloud_config->getCloudContext(),
-    'type' => $cloud_config->bundle(),
-  ]);
-
-  if (!empty($entities)) {
-    $storage->delete($entities);
-  }
-
-  // Delete cached operation data, if exists.
-  $cache_service = \Drupal::service('cloud.cache');
-  $cache_service->clearOperationAccessCache($cloud_config->getCloudContext());
+  \Drupal::service(CloudHooks::class)->cloudConfigPredelete($cloud_config);
 }
 
 /**
  * Implements hook_ENTITY_TYPE_predelete().
  */
+#[LegacyHook]
 function cloud_user_predelete($account): void {
-  // Reassign entities from the user being deleted.
-  $cloud_config_ids = \Drupal::entityTypeManager()
-    ->getStorage('cloud_config')
-    ->userRevisionIds($account);
-
-  // Reassign cloud config entities.
-  \Drupal::service('cloud')->reassignUids($cloud_config_ids, [
-    'uid' => 0,
-    'revision_uid' => 0,
-  ], 'cloud_config', TRUE, $account);
-
-  $cloud_project_ids = \Drupal::entityTypeManager()
-    ->getStorage('cloud_project')
-    ->userRevisionIds($account);
-
-  // Reassign cloud project entities.
-  \Drupal::service('cloud')->reassignUids($cloud_project_ids, [
-    'uid' => 0,
-    'revision_uid' => 0,
-  ], 'cloud_project', TRUE, $account);
-
-  // Reassign cloud store entities.
-  $cloud_store_ids = \Drupal::entityTypeManager()
-    ->getStorage('cloud_store')
-    ->getQuery()
-    ->accessCheck(TRUE)
-    ->condition('uid', $account->id())
-    ->execute();
-
-  \Drupal::service('cloud')->reassignUids($cloud_store_ids, [
-    'uid' => 0,
-  ], 'cloud_store', FALSE, $account);
-
+  \Drupal::service(CloudHooks::class)->userPredelete($account);
 }
 
 /**
@@ -543,17 +298,7 @@ function cloud_user_predelete($account): void {
  * Add additional description informing users that cloud related
  * entities are not deleted from the system.  They will be reassigned.
  */
+#[LegacyHook]
 function cloud_user_cancel_methods_alter(&$methods): void {
-  $user_settings = \Drupal::config('user.settings');
-  $anonymous_name = $user_settings->get('anonymous');
-
-  $methods['user_cancel_block']['title'] = t('Disable the account and keep its content. Entities provided by Cloud modules are not affected.');
-  $methods['user_cancel_block']['description'] = t('Your account will be blocked and you will no longer be able to log in. All of your content will remain attributed to your username. Entities provided by Cloud modules are not affected and remain attributed to your username.');
-  $methods['user_cancel_block_unpublish']['title'] = t('Disable the account and unpublish its content. Entities provided by Cloud modules are not affected');
-  $methods['user_cancel_block_unpublish']['description'] = t('Your account will be blocked and you will no longer be able to log in. All of your content will be hidden from everyone but administrators. Entities provided by Cloud modules not affected and remain attributed to your username.');
-
-  $methods['user_cancel_reassign']['title'] = t('Delete the account and make its content belong to the %anonymous-name user. Entities provided by Cloud modules are reassigned to %anonymous-name user. This action cannot be undone.', ['%anonymous-name' => $anonymous_name]);
-  $methods['user_cancel_reassign']['description'] = t('Your account will be removed and all account information deleted. All of your content will be assigned to the %anonymous-name user.  Entities provided by Cloud modules are reassigned to %anonymous-name user.', ['%anonymous-name' => $anonymous_name]);
-  $methods['user_cancel_delete']['title'] = t('Delete the account and its content. Entities provided by Cloud modules are reassigned to %anonymous-name user. This action cannot be undone.', ['%anonymous-name' => $anonymous_name]);
-  $methods['user_cancel_delete']['description'] = t('Your account will be removed and all account information deleted. All of your content will also be deleted. Entities provided by Cloud modules are not deleted but reassigned to %anonymous-name user.', ['%anonymous-name' => $anonymous_name]);
+  \Drupal::service(CloudHooks::class)->userCancelMethodsAlter($methods);
 }
diff --git a/cloud.services.yml b/cloud.services.yml
index a175ddc..1843aeb 100644
--- a/cloud.services.yml
+++ b/cloud.services.yml
@@ -48,3 +48,7 @@ services:
   cloud.cache:
     class: Drupal\cloud\Service\CloudCacheService
     arguments: ['@cache.default', '@plugin.manager.cloud_config_plugin']
+
+  Drupal\cloud\Hook\CloudHooks:
+    class: Drupal\cloud\Hook\CloudHooks
+    autowire: true
diff --git a/modules/cloud_budget/cloud_budget.module b/modules/cloud_budget/cloud_budget.module
index cbcf470..0fe7af8 100644
--- a/modules/cloud_budget/cloud_budget.module
+++ b/modules/cloud_budget/cloud_budget.module
@@ -5,6 +5,8 @@
  * Cloud Budget module.
  */
 
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\cloud_budget\Hook\CloudBudgetHooks;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\user\Entity\User;
@@ -12,57 +14,33 @@ use Drupal\user\Entity\User;
 /**
  * Implements hook_help().
  */
+#[LegacyHook]
 function cloud_budget_help($route_name, RouteMatchInterface $route_match): string {
-  switch ($route_name) {
-    case 'help.page.cloud_budget':
-      $output = '<h3>' . t('About') . '</h3>';
-      $output .= '<ul>';
-      $output .= '<li>' . \Drupal::service('cloud')->getVersion() . '</li>';
-      $output .= '<li>' . t('The cloud budget module allows users to manage Cloud Budget.') . '</li>';
-      $output .= '</ul>';
-      $output .= '<p>' . t('For more information, see the <a href=":cloud_documentation">online documentation for the Cloud Budget module</a>.', [':cloud_documentation' => 'https://www.drupal.org/docs/8/modules/cloud']) . '</p>';
-      return $output;
-
-    default:
-      return '';
-  }
+  return \Drupal::service(CloudBudgetHooks::class)->help($route_name, $route_match);
 }
 
 /**
  * Implements hook_form_alter().
  */
+#[LegacyHook]
 function cloud_budget_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
-  if (strpos($form_id, 'views_form_cloud_credit_') === 0) {
-    $form['#submit'][] = 'cloud_views_bulk_form_submit';
-  }
+  \Drupal::service(CloudBudgetHooks::class)->formAlter($form, $form_state, $form_id);
 }
 
 /**
  * Implements hook_cron().
  */
+#[LegacyHook]
 function cloud_budget_cron() {
-  // Update credits.
-  cloud_budget_update_credits();
+  \Drupal::service(CloudBudgetHooks::class)->cron();
 }
 
 /**
  * Implements hook_ENTITY_TYPE_predelete().
  */
+#[LegacyHook]
 function cloud_budget_user_predelete($account): void {
-  // Reassign cloud store entities.
-  $cloud_store_ids = \Drupal::entityTypeManager()
-    ->getStorage('cloud_credit')
-    ->getQuery()
-    ->accessCheck(TRUE)
-    ->condition('uid', $account->id())
-    ->execute();
-
-  \Drupal::service('cloud')->reassignUids($cloud_store_ids, [
-    'uid' => 0,
-  ], 'cloud_credit', FALSE, $account, [
-    // Do additional processing specific to cloud_credit entities.
-    'cloud_budget_delete_cloud_credit_user_callback',
-  ]);
+  \Drupal::service(CloudBudgetHooks::class)->userPredelete($account);
 }
 
 /**
diff --git a/modules/cloud_budget/cloud_budget.services.yml b/modules/cloud_budget/cloud_budget.services.yml
index f581114..b1b373c 100644
--- a/modules/cloud_budget/cloud_budget.services.yml
+++ b/modules/cloud_budget/cloud_budget.services.yml
@@ -4,3 +4,7 @@ services:
     parent: default_plugin_manager
     tags:
       - { name: plugin_manager_cache_clear }
+
+  Drupal\cloud_budget\Hook\CloudBudgetHooks:
+    class: Drupal\cloud_budget\Hook\CloudBudgetHooks
+    autowire: true
diff --git a/modules/cloud_budget/src/Hook/CloudBudgetHooks.php b/modules/cloud_budget/src/Hook/CloudBudgetHooks.php
new file mode 100644
index 0000000..f9cfc1f
--- /dev/null
+++ b/modules/cloud_budget/src/Hook/CloudBudgetHooks.php
@@ -0,0 +1,72 @@
+<?php
+
+namespace Drupal\cloud_budget\Hook;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for cloud_budget.
+ */
+class CloudBudgetHooks {
+  use StringTranslationTrait;
+
+  /**
+   * Implements hook_help().
+   */
+  #[Hook('help')]
+  public function help($route_name, RouteMatchInterface $route_match): string {
+    switch ($route_name) {
+      case 'help.page.cloud_budget':
+        $output = '<h3>' . $this->t('About') . '</h3>';
+        $output .= '<ul>';
+        $output .= '<li>' . \Drupal::service('cloud')->getVersion() . '</li>';
+        $output .= '<li>' . $this->t('The cloud budget module allows users to manage Cloud Budget.') . '</li>';
+        $output .= '</ul>';
+        $output .= '<p>' . $this->t('For more information, see the <a href=":cloud_documentation">online documentation for the Cloud Budget module</a>.', [
+          ':cloud_documentation' => 'https://www.drupal.org/docs/8/modules/cloud',
+        ]) . '</p>';
+        return $output;
+
+      default:
+        return '';
+    }
+  }
+
+  /**
+   * Implements hook_form_alter().
+   */
+  #[Hook('form_alter')]
+  public static function formAlter(array &$form, FormStateInterface $form_state, $form_id) {
+    if (strpos($form_id, 'views_form_cloud_credit_') === 0) {
+      $form['#submit'][] = 'cloud_views_bulk_form_submit';
+    }
+  }
+
+  /**
+   * Implements hook_cron().
+   */
+  #[Hook('cron')]
+  public static function cron() {
+    // Update credits.
+    cloud_budget_update_credits();
+  }
+
+  /**
+   * Implements hook_ENTITY_TYPE_predelete().
+   */
+  #[Hook('user_predelete')]
+  public static function userPredelete($account): void {
+    // Reassign cloud store entities.
+    $cloud_store_ids = \Drupal::entityTypeManager()->getStorage('cloud_credit')->getQuery()->accessCheck(TRUE)->condition('uid', $account->id())->execute();
+    \Drupal::service('cloud')->reassignUids($cloud_store_ids, [
+      'uid' => 0,
+    ], 'cloud_credit', FALSE, $account, [
+          // Do additional processing specific to cloud_credit entities.
+      'cloud_budget_delete_cloud_credit_user_callback',
+    ]);
+  }
+
+}
diff --git a/modules/cloud_cluster_worker/cloud_cluster_worker.module b/modules/cloud_cluster_worker/cloud_cluster_worker.module
index 02abeda..535e4f1 100644
--- a/modules/cloud_cluster_worker/cloud_cluster_worker.module
+++ b/modules/cloud_cluster_worker/cloud_cluster_worker.module
@@ -5,75 +5,33 @@
  * Cloud Orchestrator Worker module.
  */
 
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\cloud_cluster_worker\Hook\CloudClusterWorkerHooks;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
-use Drupal\cloud\Entity\CloudContentEntityBase;
 
 /**
  * Implements hook_help().
  */
+#[LegacyHook]
 function cloud_cluster_worker_help($route_name, RouteMatchInterface $route_match): string {
-  switch ($route_name) {
-    case 'help.page.cloud_cluster_worker':
-      $output = '<h3>' . t('About') . '</h3>';
-      $output .= '<ul>';
-      $output .= '<li>' . \Drupal::service('cloud')->getVersion() . '</li>';
-      $output .= '<li>' . t('The cloud orchestrator worker module allows users to manage Cloud Orchestrator Worker.') . '</li>';
-      $output .= '</ul>';
-      $output .= '<p>' . t('For more information, see the <a href=":cloud_documentation">online documentation for the Cloud Orchestrator Worker module</a>.', [':cloud_documentation' => 'https://www.drupal.org/docs/8/modules/cloud']) . '</p>';
-      return $output;
-
-    default:
-      return '';
-  }
+  return \Drupal::service(CloudClusterWorkerHooks::class)->help($route_name, $route_match);
 }
 
 /**
  * Implements hook_cron().
  */
+#[LegacyHook]
 function cloud_cluster_worker_cron(): void {
-  \Drupal::service('cloud_cluster_worker')->syncResources();
-  \Drupal::service('cloud_cluster_worker')->receiveOperations();
+  \Drupal::service(CloudClusterWorkerHooks::class)->cron();
 }
 
 /**
  * Implements hook_entity_presave().
  */
+#[LegacyHook]
 function cloud_cluster_worker_entity_presave(EntityInterface $entity) {
-  $logger = \Drupal::logger('cloud_cluster_worker');
-
-  if (!cloud_cluster_worker_is_configured()) {
-    $logger->warning(t('The cloud cluster worker is not configured.'));
-    return;
-  }
-
-  if (!is_a($entity, CloudContentEntityBase::class)) {
-    return;
-  }
-
-  $entity_type_ids_excluded = [
-    'cloud_config',
-    'cloud_store',
-  ];
-
-  $entity_type_id = $entity->getEntityTypeId();
-  if (in_array($entity_type_id, $entity_type_ids_excluded)) {
-    return;
-  }
-
-  $manager = \Drupal::service('plugin.manager.cloud_config_plugin');
-  $manager->setCloudContext($entity->getCloudContext());
-  $cloud_config = $manager->loadConfigEntity();
-  if ($cloud_config->isRemote()) {
-    return;
-  }
-
-  \Drupal::service('cloud_cluster_worker')->sendSyncRequest(
-    $entity->getCloudContext(),
-    $entity_type_id,
-    [\Drupal::service('serializer')->serialize($entity, 'json')],
-    FALSE
-  );
+  \Drupal::service(CloudClusterWorkerHooks::class)->entityPresave($entity);
 }
 
 /**
diff --git a/modules/cloud_cluster_worker/cloud_cluster_worker.services.yml b/modules/cloud_cluster_worker/cloud_cluster_worker.services.yml
index 375ba38..b6652bc 100644
--- a/modules/cloud_cluster_worker/cloud_cluster_worker.services.yml
+++ b/modules/cloud_cluster_worker/cloud_cluster_worker.services.yml
@@ -2,3 +2,7 @@ services:
   cloud_cluster_worker:
     class: Drupal\cloud_cluster_worker\Service\CloudClusterWorkerService
     arguments: ['@entity_type.manager', '@config.factory', '@http_client', '@queue', '@serializer', '@plugin.manager.cloud_config_plugin', '@logger.factory']
+
+  Drupal\cloud_cluster_worker\Hook\CloudClusterWorkerHooks:
+    class: Drupal\cloud_cluster_worker\Hook\CloudClusterWorkerHooks
+    autowire: true
diff --git a/modules/cloud_cluster_worker/src/Hook/CloudClusterWorkerHooks.php b/modules/cloud_cluster_worker/src/Hook/CloudClusterWorkerHooks.php
new file mode 100644
index 0000000..0356ac5
--- /dev/null
+++ b/modules/cloud_cluster_worker/src/Hook/CloudClusterWorkerHooks.php
@@ -0,0 +1,80 @@
+<?php
+
+namespace Drupal\cloud_cluster_worker\Hook;
+
+use Drupal\cloud\Entity\CloudContentEntityBase;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for cloud_cluster_worker.
+ */
+class CloudClusterWorkerHooks {
+  use StringTranslationTrait;
+
+  /**
+   * Implements hook_help().
+   */
+  #[Hook('help')]
+  public function help($route_name, RouteMatchInterface $route_match): string {
+    switch ($route_name) {
+      case 'help.page.cloud_cluster_worker':
+        $output = '<h3>' . $this->t('About') . '</h3>';
+        $output .= '<ul>';
+        $output .= '<li>' . \Drupal::service('cloud')->getVersion() . '</li>';
+        $output .= '<li>' . $this->t('The cloud orchestrator worker module allows users to manage Cloud Orchestrator Worker.') . '</li>';
+        $output .= '</ul>';
+        $output .= '<p>' . $this->t('For more information, see the <a href=":cloud_documentation">online documentation for the Cloud Orchestrator Worker module</a>.', [
+          ':cloud_documentation' => 'https://www.drupal.org/docs/8/modules/cloud',
+        ]) . '</p>';
+        return $output;
+
+      default:
+        return '';
+    }
+  }
+
+  /**
+   * Implements hook_cron().
+   */
+  #[Hook('cron')]
+  public static function cron(): void {
+    \Drupal::service('cloud_cluster_worker')->syncResources();
+    \Drupal::service('cloud_cluster_worker')->receiveOperations();
+  }
+
+  /**
+   * Implements hook_entity_presave().
+   */
+  #[Hook('entity_presave')]
+  public function entityPresave(EntityInterface $entity) {
+    $logger = \Drupal::logger('cloud_cluster_worker');
+    if (!cloud_cluster_worker_is_configured()) {
+      $logger->warning($this->t('The cloud cluster worker is not configured.'));
+      return;
+    }
+    if (!is_a($entity, CloudContentEntityBase::class)) {
+      return;
+    }
+    $entity_type_ids_excluded = [
+      'cloud_config',
+      'cloud_store',
+    ];
+    $entity_type_id = $entity->getEntityTypeId();
+    if (in_array($entity_type_id, $entity_type_ids_excluded)) {
+      return;
+    }
+    $manager = \Drupal::service('plugin.manager.cloud_config_plugin');
+    $manager->setCloudContext($entity->getCloudContext());
+    $cloud_config = $manager->loadConfigEntity();
+    if ($cloud_config->isRemote()) {
+      return;
+    }
+    \Drupal::service('cloud_cluster_worker')->sendSyncRequest($entity->getCloudContext(), $entity_type_id, [
+      \Drupal::service('serializer')->serialize($entity, 'json'),
+    ], FALSE);
+  }
+
+}
diff --git a/modules/cloud_dashboard/cloud_dashboard.module b/modules/cloud_dashboard/cloud_dashboard.module
index 3871635..d96ef4a 100644
--- a/modules/cloud_dashboard/cloud_dashboard.module
+++ b/modules/cloud_dashboard/cloud_dashboard.module
@@ -5,85 +5,35 @@
  * Cloud Dashboard module.
  */
 
-use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\cloud_dashboard\Hook\CloudDashboardHooks;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Session\AccountInterface;
-use Drupal\cloud\Service\CloudService;
 
 /**
  * Implements hook_help().
  */
+#[LegacyHook]
 function cloud_dashboard_help($route_name, RouteMatchInterface $route_match): string {
-  switch ($route_name) {
-    case 'help.page.cloud_dashboard':
-      $output = '<h3>' . t('About') . '</h3>';
-      $output .= '<ul>';
-      $output .= '<li>' . \Drupal::service('cloud')->getVersion() . '</li>';
-      $output .= '<li>' . t('The Cloud Dashboard module creates a user interface for users to manage clouds.') . '</li>';
-      $output .= '</ul>';
-      $output .= '<p>' . t('For more information, see the <a href=":cloud_documentation">online documentation for the Cloud module</a>.', [':cloud_documentation' => 'https://www.drupal.org/docs/8/modules/cloud']) . '</p>';
-      return $output;
-
-    default:
-      return '';
-  }
+  return \Drupal::service(CloudDashboardHooks::class)->help($route_name, $route_match);
 }
 
 /**
  * Implements hook_jsonapi_entity_filter_access().
  */
+#[LegacyHook]
 function cloud_dashboard_jsonapi_entity_filter_access(EntityTypeInterface $entity_type, AccountInterface $account) {
-  $entity_type_id = CloudService::convertUnderscoreToWhitespace($entity_type->id());
-  $mapping = [
-    'cloud launch template' => 'cloud server template',
-    'openstack stack event' => 'openstack stack',
-    'openstack stack resource' => 'openstack stack',
-  ];
-
-  if (!empty($mapping[$entity_type_id])) {
-    $entity_type_id = $mapping[$entity_type_id];
-  }
-
-  $permission = 'list ' . ($entity_type_id === 'cloud launch template' ? 'cloud server template' : $entity_type_id);
-
-  return [
-    JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, $permission),
-  ];
+  return \Drupal::service(CloudDashboardHooks::class)->jsonapiEntityFilterAccess($entity_type, $account);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function cloud_dashboard_form_cloud_admin_settings_alter(array &$form, FormStateInterface $form_state): void {
-  $cloud_configs = \Drupal::entityTypeManager()
-    ->getStorage('cloud_config_type')
-    ->loadMultiple();
-
-  if (empty($cloud_configs)) {
-    return;
-  }
-
-  $config = \Drupal::configFactory()->get('cloud_dashboard.settings');
-  $form['cloud_orchestrator'] = [
-    '#type' => 'details',
-    '#open' => TRUE,
-    '#title' => t('Cloud Orchestrator'),
-  ];
-
-  $providers = [];
-  foreach ($cloud_configs ?: [] as $cloud_config) {
-    $providers[$cloud_config->id()] = $cloud_config->label();
-  }
-  $form['cloud_orchestrator']['cloud_dashboard_disabled_dashboard_tabs'] = [
-    '#title' => t('Disable dashboard tabs'),
-    '#description' => t('Check cloud service provider(s) to disable their blocks from the dashboard.'),
-    '#type' => 'checkboxes',
-    '#options' => $providers,
-    '#default_value' => $config->get('cloud_dashboard_disabled_dashboard_tabs') ?: [],
-  ];
-  $form['#submit'][] = 'cloud_dashboard_form_cloud_admin_settings_form_submit';
+  \Drupal::service(CloudDashboardHooks::class)->formCloudAdminSettingsAlter($form, $form_state);
 }
 
 /**
@@ -103,15 +53,9 @@ function cloud_dashboard_form_cloud_admin_settings_form_submit(array $form, Form
 /**
  * Implements hook_theme().
  */
+#[LegacyHook]
 function cloud_dashboard_theme($existing, $type, $theme, $path) {
-  return [
-    'tabbed_dashboard' => [
-      'variables' => [
-        'top' => NULL,
-        'content' => NULL,
-      ],
-    ],
-  ];
+  return \Drupal::service(CloudDashboardHooks::class)->theme($existing, $type, $theme, $path);
 }
 
 /**
diff --git a/modules/cloud_dashboard/cloud_dashboard.services.yml b/modules/cloud_dashboard/cloud_dashboard.services.yml
new file mode 100644
index 0000000..ca89c4f
--- /dev/null
+++ b/modules/cloud_dashboard/cloud_dashboard.services.yml
@@ -0,0 +1,5 @@
+
+services:
+  Drupal\cloud_dashboard\Hook\CloudDashboardHooks:
+    class: Drupal\cloud_dashboard\Hook\CloudDashboardHooks
+    autowire: true
diff --git a/modules/cloud_dashboard/src/Hook/CloudDashboardHooks.php b/modules/cloud_dashboard/src/Hook/CloudDashboardHooks.php
new file mode 100644
index 0000000..bbf2bbb
--- /dev/null
+++ b/modules/cloud_dashboard/src/Hook/CloudDashboardHooks.php
@@ -0,0 +1,109 @@
+<?php
+
+namespace Drupal\cloud_dashboard\Hook;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Access\AccessResult;
+use Drupal\jsonapi\JsonApiFilter;
+use Drupal\Component\Utility\DeprecationHelper;
+use Drupal\cloud\Service\CloudService;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for cloud_dashboard.
+ */
+class CloudDashboardHooks {
+  use StringTranslationTrait;
+
+  /**
+   * Implements hook_help().
+   */
+  #[Hook('help')]
+  public function help($route_name, RouteMatchInterface $route_match): string {
+    switch ($route_name) {
+      case 'help.page.cloud_dashboard':
+        $output = '<h3>' . $this->t('About') . '</h3>';
+        $output .= '<ul>';
+        $output .= '<li>' . \Drupal::service('cloud')->getVersion() . '</li>';
+        $output .= '<li>' . $this->t('The Cloud Dashboard module creates a user interface for users to manage clouds.') . '</li>';
+        $output .= '</ul>';
+        $output .= '<p>' . $this->t('For more information, see the <a href=":cloud_documentation">online documentation for the Cloud module</a>.', [
+          ':cloud_documentation' => 'https://www.drupal.org/docs/8/modules/cloud',
+        ]) . '</p>';
+        return $output;
+
+      default:
+        return '';
+    }
+  }
+
+  /**
+   * Implements hook_jsonapi_entity_filter_access().
+   */
+  #[Hook('jsonapi_entity_filter_access')]
+  public static function jsonapiEntityFilterAccess(EntityTypeInterface $entity_type, AccountInterface $account) {
+    $entity_type_id = CloudService::convertUnderscoreToWhitespace($entity_type->id());
+    $mapping = [
+      'cloud launch template' => 'cloud server template',
+      'openstack stack event' => 'openstack stack',
+      'openstack stack resource' => 'openstack stack',
+    ];
+    if (!empty($mapping[$entity_type_id])) {
+      $entity_type_id = $mapping[$entity_type_id];
+    }
+    $permission = 'list ' . ($entity_type_id === 'cloud launch template' ? 'cloud server template' : $entity_type_id);
+    return [
+      DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.3.0', fn() => JsonApiFilter::AMONG_ALL,
+    fn() => JSONAPI_FILTER_AMONG_ALL) => AccessResult::allowedIfHasPermission($account, $permission),
+    ];
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_admin_settings_alter')]
+  public function formCloudAdminSettingsAlter(array &$form, FormStateInterface $form_state): void {
+    $cloud_configs = \Drupal::entityTypeManager()->getStorage('cloud_config_type')->loadMultiple();
+    if (empty($cloud_configs)) {
+      return;
+    }
+    $config = \Drupal::configFactory()->get('cloud_dashboard.settings');
+    $form['cloud_orchestrator'] = [
+      '#type' => 'details',
+      '#open' => TRUE,
+      '#title' => $this->t('Cloud Orchestrator'),
+    ];
+    $providers = [];
+    foreach ($cloud_configs ?: [] as $cloud_config) {
+      $providers[$cloud_config->id()] = $cloud_config->label();
+    }
+    $form['cloud_orchestrator']['cloud_dashboard_disabled_dashboard_tabs'] = [
+      '#title' => $this->t('Disable dashboard tabs'),
+      '#description' => $this->t('Check cloud service provider(s) to disable their blocks from the dashboard.'),
+      '#type' => 'checkboxes',
+      '#options' => $providers,
+      '#default_value' => $config->get('cloud_dashboard_disabled_dashboard_tabs') ?: [],
+    ];
+    $form['#submit'][] = 'cloud_dashboard_form_cloud_admin_settings_form_submit';
+  }
+
+  /**
+   * Implements hook_theme().
+   */
+  #[Hook('theme')]
+  public static function theme($existing, $type, $theme, $path) {
+    return [
+      'tabbed_dashboard' => [
+        'variables' => [
+          'top' => NULL,
+          'content' => NULL,
+        ],
+      ],
+    ];
+  }
+
+}
diff --git a/modules/cloud_service_providers/aws_cloud/aws_cloud.install b/modules/cloud_service_providers/aws_cloud/aws_cloud.install
index 02cd0be..ea68da0 100644
--- a/modules/cloud_service_providers/aws_cloud/aws_cloud.install
+++ b/modules/cloud_service_providers/aws_cloud/aws_cloud.install
@@ -5,6 +5,7 @@
  * Install and updates for aws_cloud.
  */
 
+use Drupal\Component\Utility\DeprecationHelper;
 use Drupal\Component\Serialization\Yaml;
 use Drupal\Core\Config\FileStorage;
 use Drupal\Core\Database\Database;
@@ -213,7 +214,7 @@ function aws_cloud_update_8109(): void {
   $config_factory = \Drupal::configFactory();
   $view = $config_factory->getEditable('views.view.aws_snapshot');
   $view->set('display.default.display_options.fields.name.settings.link_to_entity', TRUE);
-  $view->save(TRUE);
+  DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => $view->save(), fn() => $view->save(TRUE));
 }
 
 /**
@@ -238,12 +239,12 @@ function aws_cloud_update_8110(): void {
   foreach ($field_names ?: [] as $field_name) {
     $field = $config_factory->getEditable("field.field.cloud_launch_template.aws_cloud.$field_name");
     $field->set('required', TRUE);
-    $field->save(TRUE);
+    DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => $field->save(), fn() => $field->save(TRUE));
   }
 
   $field = $config_factory->getEditable('field.field.cloud_launch_template.aws_cloud.field_network');
   $field->set('label', t('Network interface'));
-  $field->save(TRUE);
+  DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => $field->save(), fn() => $field->save(TRUE));
 }
 
 /**
@@ -279,7 +280,7 @@ function aws_cloud_update_8113(): void {
     $new_allowed_values[] = $allowed_value;
   }
   $field->set('settings.allowed_values', $new_allowed_values);
-  $field->save(TRUE);
+  DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => $field->save(), fn() => $field->save(TRUE));
 }
 
 /**
@@ -337,7 +338,7 @@ function aws_cloud_update_8116(): void {
     $field = $config_factory->getEditable($field_config_name);
     $field->set('settings.allowed_values', $allowed_values);
     $field->set('settings.allowed_values_function', $allowed_values_function);
-    $field->save(TRUE);
+    DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => $field->save(), fn() => $field->save(TRUE));
   }
 }
 
@@ -392,7 +393,7 @@ function aws_cloud_update_8121(): void {
   $template = $config_factory->getEditable('core.entity_view_display.cloud_launch_template.aws_cloud.default');
   $template->set('content.field_instance_type.label', 'inline');
   $template->set('content.field_instance_type.type', 'list_key');
-  $template->save(TRUE);
+  DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => $template->save(), fn() => $template->save(TRUE));
 }
 
 /**
@@ -417,13 +418,13 @@ function aws_cloud_update_8122(): void {
   $field->set('label', 'Secret access key');
   $field->set('description', 'e.g. 123ABC/defGHIjkl34+LMNopq567RSTuvwxYz89Z. Enter 16-32 Characters.  If <em>assume role</em> is selected, enter the secret access key of the child user.');
   $field->set('required', FALSE);
-  $field->save(TRUE);
+  DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => $field->save(), fn() => $field->save(TRUE));
 
   $field = $config_factory->getEditable('field.field.cloud_config.aws_cloud.field_access_key');
   $field->set('label', 'Access key ID');
   $field->set('description', 'Enter 16-32 Characters, e.g. 12ABCDEFGHIJKVWXYZ89.  If <em>assume role</em> is selected, enter the access key ID of the child user.');
   $field->set('required', FALSE);
-  $field->save(TRUE);
+  DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => $field->save(), fn() => $field->save(TRUE));
 
   // Update any existing entities.
   $entities = \Drupal::service('plugin.manager.cloud_config_plugin')->loadConfigEntities('aws_cloud');
@@ -496,7 +497,7 @@ function aws_cloud_update_8124(): void {
   foreach ($field_names ?: [] as $field_name) {
     $template->set("content.{$field_name}.label", 'inline');
   }
-  $template->save(TRUE);
+  DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => $template->save(), fn() => $template->save(TRUE));
 }
 
 /**
@@ -3405,12 +3406,12 @@ function aws_cloud_update_8257(): void {
   foreach ($field_names ?: [] as $field_name) {
     $field = $config_factory->getEditable("field.field.cloud_launch_template.aws_cloud.$field_name");
     $field->set('required', TRUE);
-    $field->save(TRUE);
+    DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => $field->save(), fn() => $field->save(TRUE));
   }
 
   $field = $config_factory->getEditable('field.field.cloud_launch_template.aws_cloud.field_network');
   $field->set('label', t('Network interface'));
-  $field->save(TRUE);
+  DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => $field->save(), fn() => $field->save(TRUE));
 
   if (empty(FieldConfig::loadByName('cloud_launch_template', 'aws_cloud', 'field_workflow_status'))) {
 
diff --git a/modules/cloud_service_providers/aws_cloud/aws_cloud.module b/modules/cloud_service_providers/aws_cloud/aws_cloud.module
index 9223a5c..719a382 100644
--- a/modules/cloud_service_providers/aws_cloud/aws_cloud.module
+++ b/modules/cloud_service_providers/aws_cloud/aws_cloud.module
@@ -8,6 +8,8 @@
  * clouds. AWS Cloud is Amazon EC2.
  */
 
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\aws_cloud\Hook\AwsCloudHooks;
 use Aws\Exception\AwsException;
 use Aws\Exception\CredentialsException;
 use Drupal\Component\Utility\Random;
@@ -16,8 +18,6 @@ use Drupal\Core\Ajax\ReplaceCommand;
 use Drupal\Core\Ajax\SettingsCommand;
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Database\Query\AlterableInterface;
-use Drupal\Core\Database\Query\Condition;
-use Drupal\Core\Database\Query\SelectInterface;
 use Drupal\Core\Datetime\DrupalDateTime;
 use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
@@ -54,46 +54,9 @@ use Symfony\Component\HttpFoundation\RedirectResponse;
 /**
  * Implements hook_help().
  */
+#[LegacyHook]
 function aws_cloud_help($route_name, RouteMatchInterface $route_match): string {
-  $output = '';
-  switch ($route_name) {
-    case 'help.page.aws_cloud':
-      $output = '<h3>' . t('About') . '</h3>';
-      $output .= '<ul>';
-      $output .= '<li>' . \Drupal::service('cloud')->getVersion() . '</li>';
-      $output .= '<li>' . t('AWS Cloud (aws_cloud module) creates a user interface for managing AWS-related clouds. AWS Cloud is defined as Amazon EC2.') . '</li>';
-      $output .= '</ul>';
-      $output .= '<h3>' . t('Features') . '</h3>';
-      $output .= '<ul>';
-      $output .= '<li>' . t('<strong>EC2</strong>');
-      $output .= '<ul>';
-      $output .= '<li>' . t('Manage EC2.') . '</li>';
-      $output .= '<li>' . t('Integrate with AWS instance scheduler.') . '</li>';
-      $output .= '<li>' . t('Support EC2 launch templates.') . '</li>';
-      $output .= '<li>' . t('Support automatic termination scheduling.') . '</li>';
-      $output .= '<li>' . t('Support instance profile (instance IAM role), assume role and switch role') . '</li>';
-      $output .= '</ul></li></ul>';
-      $output .= '<ul>';
-      $output .= '<li>' . t('<strong>VPC</strong>');
-      $output .= '<ul>';
-      $output .= '<li>' . t('Manage VPC.') . '</li>';
-      $output .= '<li>' . t('Create a VPC automatically based on each login user account.') . '</li>';
-      $output .= '<li>' . t('Create and manage bastion for each VPC.') . '</li>';
-      $output .= '<li>' . t('Support VPC flow logs.') . '</li>';
-      $output .= '<li>' . t('Support Cloud Formation templates to deploy a Cloud Orchestrator distribution.') . '</li>';
-      $output .= '</ul></li></ul>';
-      $output .= '<ul>';
-      $output .= '<li>' . t('<strong>Cost</strong>');
-      $output .= '<ul>';
-      $output .= '<li>' . t('List up instance type specs and costs.') . '</li>';
-      $output .= '<li>' . t('Create Google Spreadsheets for instance type specs and costs.') . '</li>';
-      $output .= '<li>' . t('Send reminder alerts for long-running instances, unused volumes and snapshots.') . '</li>';
-      $output .= '<li>' . t('Manage budgets and credits.') . '</li>';
-      $output .= '</ul></li></ul>';
-      $output .= '<p>' . t('For more information, see the <a href=":cloud_documentation">online documentation for the AWS Cloud module</a>.', [':cloud_documentation' => 'https://www.drupal.org/docs/8/modules/cloud']) . '</p>';
-
-  }
-  return $output;
+  return \Drupal::service(AwsCloudHooks::class)->help($route_name, $route_match);
 }
 
 /**
@@ -494,29 +457,9 @@ function aws_cloud_entity_type_alter(array &$entity_types): void {
 /**
  * Implements hook_ENTITY_TYPE_predelete().
  */
+#[LegacyHook]
 function aws_cloud_user_predelete($account): void {
-  /** @var Drupal\cloud\Service\CloudService $cloud_service */
-  $cloud_service = \Drupal::service('cloud');
-  $entity_types = $cloud_service->getProviderEntityTypes('aws_cloud');
-  foreach ($entity_types ?: [] as $entity_type) {
-    if (empty($entity_type)) {
-      continue;
-    }
-    $ids = \Drupal::entityTypeManager()
-      ->getStorage($entity_type->id())
-      ->getQuery()
-      ->accessCheck(TRUE)
-      ->condition('uid', $account->id())
-      ->execute();
-    if (count($ids) === 0) {
-      continue;
-    }
-    $cloud_service->reassignUids($ids, [
-      'uid' => 0,
-    ], $entity_type->id(), FALSE, $account, [
-      'aws_cloud_reassign_entity_uid_callback',
-    ]);
-  }
+  \Drupal::service(AwsCloudHooks::class)->userPredelete($account);
 }
 
 /**
@@ -627,121 +570,41 @@ function aws_cloud_get_id_method(string $entity_type): string {
 /**
  * Implements hook_entity_view().
  */
+#[LegacyHook]
 function aws_cloud_entity_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode): void {
-  if (!empty($entity) && $view_mode === 'full'
-  && $entity->getEntityTypeId() === 'aws_cloud_key_pair'
-  && $entity->id() !== NULL) {
-    /** @var \Drupal\aws_cloud\Entity\Ec2\KeyPairInterface|null $keypair */
-    $keypair = \Drupal::entityTypeManager()->getStorage('aws_cloud_key_pair')->load($entity->id());
-
-    // If the key is on the server, prompt user to download it.
-    if (!empty($keypair) && !empty($keypair->getKeyMaterial())) {
-      $url = Url::fromRoute('entity.aws_cloud_key_pair.download', [
-        'cloud_context' => $keypair->getCloudContext(),
-        'key_pair' => $keypair->id(),
-      ])->toString();
-
-      $build['#attached']['drupalSettings']['download_url'] = $url;
-      $build['#attached']['library'][] = 'aws_cloud/aws_cloud_key_pair_download';
-    }
-  }
+  \Drupal::service(AwsCloudHooks::class)->entityView($build, $entity, $display, $view_mode);
 }
 
 /**
  * Implements hook_cron().
  */
+#[LegacyHook]
 function aws_cloud_cron(): void {
-  $ec2_service = \Drupal::service('aws_cloud.ec2');
-  $cfn_service = \Drupal::service('aws_cloud.cloud_formation');
-
-  /** @var \Drupal\aws_cloud\Access\AwsCloudAccess $aws_access_service */
-  $aws_access_service = \Drupal::service('aws_cloud.access');
-
-  $entities = \Drupal::service('plugin.manager.cloud_config_plugin')->loadConfigEntities('aws_cloud');
-  foreach ($entities ?: [] as $entity) {
-    if ($entity->isRemote()) {
-      continue;
-    }
-
-    $ec2_service->setCloudContext($entity->getCloudContext());
-    $ec2_service->createResourceQueueItems($entity);
-
-    $cfn_service->setCloudContext($entity->getCloudContext());
-    $cfn_service->createResourceQueueItems($entity);
-
-    // Update IAM permissions.
-    $aws_access_service->createResourceQueueItems($entity);
-
-    // @todo re-enabled instance bundling
-    // Update any instance types if needed.
-    aws_cloud_update_instance_types($entity);
-  }
-
-  // Notify owners and admins if their instances have been running for too long.
-  aws_cloud_notify_instance_owners_and_admins('long_running');
-
-  // Notify owners and admins if their instances have been running
-  // in low utilization status for several days.
-  aws_cloud_notify_instance_owners_and_admins('low_utilization');
-
-  // Notify admins about unused EBS volumes.
-  aws_cloud_notify_unused_volumes_owners_and_admins();
-
-  // Notify admins about unused EBS snapshots.
-  aws_cloud_notify_unused_snapshots_owners_and_admins();
-
-  // Notify admins about unused Elastic IPs.
-  aws_cloud_notify_unused_elastic_ips_owners_and_admins();
-
-  // Clear the cache.
-  \Drupal::service('cloud')->clearAllCacheValues();
+  \Drupal::service(AwsCloudHooks::class)->cron();
 }
 
 /**
  * Implements hook_rebuild().
  */
+#[LegacyHook]
 function aws_cloud_rebuild(): void {
-  // Rebuild the instance type cache.
-  $entities = \Drupal::service('plugin.manager.cloud_config_plugin')->loadConfigEntities('aws_cloud');
-  foreach ($entities ?: [] as $entity) {
-    // Update any instance types if needed.
-    aws_cloud_update_instance_types($entity);
-  }
+  \Drupal::service(AwsCloudHooks::class)->rebuild();
 }
 
 /**
  * Implements hook_cloud_config_presave().
  */
+#[LegacyHook]
 function aws_cloud_cloud_config_presave(CloudConfig $cloud_config): void {
-  if ($cloud_config->bundle() === 'aws_cloud') {
-    $cloud_context = $cloud_config->get('cloud_context')->value;
-    $access_key = $cloud_config->get('field_access_key')->value;
-    $secret_key = $cloud_config->get('field_secret_key')->value;
-    if (!empty($access_key) && !empty($secret_key)
-      && empty($cloud_config->get('field_use_instance_profile')->value)) {
-      // Create credential_file.
-      aws_cloud_create_credential_file($cloud_context, $access_key, $secret_key);
-
-      $cloud_config->set('field_access_key', NULL);
-      $cloud_config->set('field_secret_key', NULL);
-    }
-
-    if (!empty($cloud_config->get('field_get_price_list')->value)) {
-      // Update instance types.
-      aws_cloud_update_instance_types($cloud_config);
-    }
-
-    // Create or update a pricing spreadsheet.
-    aws_cloud_create_or_update_pricing_spreadsheet($cloud_config);
-  }
+  \Drupal::service(AwsCloudHooks::class)->cloudConfigPresave($cloud_config);
 }
 
 /**
  * Implements hook_default_cloud_config_icon().
  */
+#[LegacyHook]
 function aws_cloud_default_cloud_config_icon(EntityInterface $entity): ?int {
-  // Provides the calling hook with the default AWS icon.
-  return \Drupal::service('cloud')->getDefaultCloudConfigIcon($entity, 'aws_cloud');
+  return \Drupal::service(AwsCloudHooks::class)->defaultCloudConfigIcon($entity);
 }
 
 /**
@@ -810,32 +673,17 @@ function aws_cloud_create_or_update_pricing_spreadsheet(CloudConfig $cloud_confi
 /**
  * Implements hook_cloud_config_update().
  */
+#[LegacyHook]
 function aws_cloud_cloud_config_update(CloudConfig $cloud_config): void {
-  // Only perform resource update if coming from the cloud service provider
-  // edit form.  This hook can be triggered during user_delete().  user_delete()
-  // runs in progressive batch mode and `aws_cloud_cloud_config_update()` will
-  // try to run a second non-progressive batch mode.  This confuses Drupal
-  // and the original batch processing will never complete, causing the
-  // site to hang.
-  if ($cloud_config->bundle() === 'aws_cloud'
-  && !aws_cloud_cloud_configs_equal(
-    $cloud_config,
-    $cloud_config->original,
-    ['changed', 'field_spreadsheet_pricing_url', 'field_system_vpc']
-  )
-  && \Drupal::routeMatch()->getRouteName() === 'entity.cloud_config.edit_form') {
-    // Update resources.
-    aws_cloud_update_ec2_resources($cloud_config);
-  }
+  \Drupal::service(AwsCloudHooks::class)->cloudConfigUpdate($cloud_config);
 }
 
 /**
  * Implements hook_cloud_config_predelete().
  */
+#[LegacyHook]
 function aws_cloud_cloud_config_predelete(CloudConfig $cloud_config): void {
-  if ($cloud_config->bundle() === 'aws_cloud') {
-    \Drupal::service('cloud')->deleteQueue(\Drupal::service('cloud')->getResourceQueueName($cloud_config));
-  }
+  \Drupal::service(AwsCloudHooks::class)->cloudConfigPredelete($cloud_config);
 }
 
 /**
@@ -904,34 +752,10 @@ function aws_cloud_update_ec2_resources(CloudConfig $cloud_config): void {
 /**
  * Implements hook_cloud_config_delete().
  */
+#[LegacyHook]
 function aws_cloud_cloud_config_delete(CloudConfig $cloud_config): void {
 
-  if ($cloud_config->bundle() === 'aws_cloud') {
-
-    $cloud_context = $cloud_config->getCloudContext();
-
-    /** @var \Drupal\aws_cloud\Service\Ec2\Ec2ServiceInterface $ec2_service */
-    $ec2_service = \Drupal::service('aws_cloud.ec2');
-    try {
-      $ec2_service->setCloudContext($cloud_context);
-    }
-    catch (\Exception $e) {
-    }
-    finally {
-      \Drupal::service('cloud')->clearAllEntities('aws_cloud', $cloud_context);
-
-      // Clean up credential files.
-      $credential_file = aws_cloud_ini_file_path($cloud_config->get('cloud_context')->value);
-      \Drupal::service('file_system')->delete($credential_file);
-
-      // Delete the pricing spreadsheet.
-      aws_cloud_delete_pricing_spreadsheet($cloud_config);
-      \Drupal::service('cloud')->clearAllCacheValues();
-    }
-
-    \Drupal::service('aws_cloud.access')->deletePermissions($cloud_config->getCloudContext());
-
-  }
+  \Drupal::service(AwsCloudHooks::class)->cloudConfigDelete($cloud_config);
 }
 
 /**
@@ -972,620 +796,161 @@ function aws_cloud_delete_pricing_spreadsheet(CloudConfig $cloud_config): void {
 /**
  * Implements hook_entity_operation().
  */
+#[LegacyHook]
 function aws_cloud_entity_operation(EntityInterface $entity): array {
-  $operations = [];
-  $account = \Drupal::currentUser();
-
-  if ($entity->getEntityTypeId() === 'aws_cloud_instance') {
-    if ($account->hasPermission('edit any aws cloud instance')
-    || ($account->hasPermission('edit own aws cloud instance')
-    && !empty($entity->getOwner())
-    && $account->id() === $entity->getOwner()->id())) {
-      if ($entity->getInstanceState() === 'running') {
-        $operations['stop'] = [
-          'title' => t('Stop'),
-          'url' => $entity->toUrl('stop-form'),
-          'weight' => 20,
-        ];
-        $operations['reboot'] = [
-          'title' => t('Reboot'),
-          'url' => $entity->toUrl('reboot-form'),
-          'weight' => 21,
-        ];
-      }
-      elseif ($entity->getInstanceState() === 'stopped') {
-        $operations['start'] = [
-          'title' => t('Start'),
-          'url' => $entity->toUrl('start-form'),
-          'weight' => 20,
-        ];
-
-        // Add associate IP link.
-        if (aws_cloud_can_attach_ip($entity) === TRUE
-        && count(aws_cloud_get_available_elastic_ips($entity->getCloudContext()))) {
-          $operations['associate_elastic_ip'] = [
-            'title' => t('Associate Elastic IP'),
-            'url' => $entity->toUrl('associate-elastic-ip-form'),
-            'weight' => 22,
-          ];
-        }
-      }
-      elseif ($entity->getInstanceState() === 'shutting-down'
-        || $entity->getInstanceState() === 'terminated') {
-        return $operations;
-      }
-      // Create Image.
-      $operations['create_image'] = [
-        'title' => t('Create image'),
-        'url' => $entity->toUrl('create-image-form'),
-        'weight' => 21,
-      ];
-    }
-  }
-  elseif ($entity->getEntityTypeId() === 'aws_cloud_volume') {
-    if ($account->hasPermission('edit any aws cloud volume')
-    || ($account->hasPermission('edit own aws cloud volume')
-    && !empty($entity->getOwner())
-    && $account->id() === $entity->getOwner()->id())) {
-      if ($entity->getState() === 'available') {
-        $operations['attach'] = [
-          'title' => t('Attach'),
-          'url' => $entity->toUrl('attach-form'),
-          'weight' => 20,
-        ];
-      }
-      elseif ($entity->getState() === 'in-use') {
-        $operations['detach'] = [
-          'title' => t('Detach'),
-          'url' => $entity->toUrl('detach-form'),
-          'weight' => 20,
-        ];
-      }
-    }
-
-    $url = Url::fromRoute('entity.aws_cloud_snapshot.add_form', [
-      'cloud_context' => $entity->getCloudContext(),
-    ]);
-    if ($url->access($account)) {
-      $operations['create_snapshot'] = [
-        'title' => t('Create snapshot'),
-        'url' => Url::fromRoute(
-          'entity.aws_cloud_snapshot.add_form',
-          [
-            'cloud_context' => $entity->getCloudContext(),
-            'volume_id' => $entity->getVolumeId(),
-          ]
-        ),
-        'weight' => 21,
-      ];
-    }
-  }
-  elseif ($entity->getEntityTypeId() === 'aws_cloud_elastic_ip') {
-    if ($account->hasPermission('edit any aws cloud elastic ip')
-    || ($account->hasPermission('edit own aws cloud elastic ip')
-    && !empty($entity->getOwner())
-    && $account->id() === $entity->getOwner()->id())) {
-      if ($entity->getAssociationId() === NULL) {
-        $operations['associate'] = [
-          'title' => t('Associate'),
-          'url' => $entity->toUrl('associate-form'),
-        ];
-      }
-      else {
-        $operations['disassociate'] = [
-          'title' => t('Disassociate'),
-          'url' => $entity->toUrl('disassociate-form'),
-        ];
-      }
-    }
-  }
-  elseif ($entity->getEntityTypeId() === 'aws_cloud_snapshot') {
-    $url = Url::fromRoute('entity.aws_cloud_volume.add_form', [
-      'cloud_context' => $entity->getCloudContext(),
-    ]);
-    if ($url->access($account)) {
-      $operations['create_volume'] = [
-        'title' => t('Create Volume'),
-        'url' => Url::fromRoute(
-          'entity.aws_cloud_volume.add_form',
-          [
-            'cloud_context' => $entity->getCloudContext(),
-            'snapshot_id' => $entity->getSnapshotId(),
-          ]
-        ),
-        'weight' => 20,
-      ];
-    }
-  }
-  elseif ($entity->getEntityTypeId() === 'aws_cloud_security_group') {
-    if ($account->hasPermission('add aws cloud security group')) {
-      $operations['copy_security_group'] = [
-        'title' => t('Copy'),
-        'url' => Url::fromRoute(
-          'entity.aws_cloud_security_group.copy_form',
-          [
-            'cloud_context' => $entity->getCloudContext(),
-            'aws_cloud_security_group' => $entity->id(),
-          ]
-        ),
-        'weight' => 20,
-      ];
-    }
-  }
-  elseif ($entity->getEntityTypeId() === 'aws_cloud_vpc_peering_connection') {
-    if ($account->hasPermission('edit any aws cloud vpc peering connection')
-    || ($account->hasPermission('edit own aws cloud vpc peering connection')
-    && $entity->getStatusCode() === 'pending-acceptance')) {
-      $operations['accept_vpc_peering_connection'] = [
-        'title' => t('Accept'),
-        'url' => Url::fromRoute(
-          'entity.aws_cloud_vpc_peering_connection.accept_form',
-          [
-            'cloud_context' => $entity->getCloudContext(),
-            'aws_cloud_vpc_peering_connection' => $entity->id(),
-          ]
-        ),
-        'weight' => 20,
-      ];
-    }
-  }
-  elseif ($entity->getEntityTypeId() === 'aws_cloud_internet_gateway') {
-    if ($account->hasPermission('edit any aws cloud internet gateway')
-    || ($account->hasPermission('edit own aws cloud internet gateway')
-    && !empty($entity->getOwner())
-    && $account->id() === $entity->getOwner()->id())) {
-      if ($entity->getVpcId() === NULL) {
-        $operations['attach'] = [
-          'title' => t('Attach'),
-          'url' => $entity->toUrl('attach-form'),
-        ];
-      }
-      else {
-        $operations['detach'] = [
-          'title' => t('Detach'),
-          'url' => $entity->toUrl('detach-form'),
-        ];
-      }
-    }
-  }
-
-  return $operations;
+  return \Drupal::service(AwsCloudHooks::class)->entityOperation($entity);
 }
 
 /**
  * Implements hook_entity_operation_alter().
  */
+#[LegacyHook]
 function aws_cloud_entity_operation_alter(array &$operations, EntityInterface $entity): void {
-
-  if ($entity->getEntityTypeId() === 'aws_cloud_volume') {
-    if ($entity->getState() === 'in-use') {
-      unset($operations['delete']);
-    }
-  }
-
-  if ($entity->getEntityTypeId() === 'aws_cloud_image') {
-    if ($entity->getStatus() === 'pending') {
-      unset($operations['delete']);
-    }
-  }
-
-  if ($entity->getEntityTypeId() === 'aws_cloud_instance') {
-    if ($entity->getInstanceState() === 'shutting-down'
-      || $entity->getInstanceState() === 'terminated') {
-      unset($operations['edit']);
-      unset($operations['delete']);
-    }
-    if (isset($operations['edit'])) {
-      /** @var Drupal\Core\Url $url */
-      $url = $operations['edit']['url'];
-      $url->setOption('query', []);
-    }
-  }
-
-  if ($entity->getEntityTypeId() === 'aws_cloud_elastic_ip') {
-    $association_id = $entity->getAssociationId();
-    if (isset($association_id)) {
-      unset($operations['delete']);
-    }
-  }
+  \Drupal::service(AwsCloudHooks::class)->entityOperationAlter($operations, $entity);
 }
 
 /**
  * Implements hook_query_TAG_Alter().
  */
+#[LegacyHook]
 function aws_cloud_query_aws_cloud_instance_views_access_alter(AlterableInterface $query): void {
-  if (!$account = $query->getMetaData('account')) {
-    $account = \Drupal::currentUser();
-  }
-  if ($account->hasPermission('view any aws cloud instance')) {
-    return;
-  }
-  else {
-    // Add a 'uid' condition.
-    $query->condition('aws_cloud_instance.uid', $account->id());
-  }
-
+  \Drupal::service(AwsCloudHooks::class)->queryAwsCloudInstanceViewsAccessAlter($query);
 }
 
 /**
  * Implements hook_query_TAG_Alter().
  */
+#[LegacyHook]
 function aws_cloud_query_aws_cloud_image_views_access_alter(AlterableInterface $query): void {
-  if (!$account = $query->getMetaData('account')) {
-    $account = \Drupal::currentUser();
-  }
-  if ($account->hasPermission('view any aws cloud image')) {
-    return;
-  }
-  else {
-    // Add a 'uid' condition and let users view public images.
-    $or = new Condition('OR');
-    $or->condition('aws_cloud_image.uid', [0, $account->id()], 'IN')
-      ->condition('aws_cloud_image.visibility', TRUE);
-    $query->condition($or);
-  }
-
+  \Drupal::service(AwsCloudHooks::class)->queryAwsCloudImageViewsAccessAlter($query);
 }
 
 /**
  * Implements hook_query_TAG_Alter().
  */
+#[LegacyHook]
 function aws_cloud_query_aws_cloud_security_group_views_access_alter(AlterableInterface $query): void {
-  if (!$account = $query->getMetaData('account')) {
-    $account = \Drupal::currentUser();
-  }
-  if ($account->hasPermission('view any aws cloud security group')) {
-    return;
-  }
-  else {
-    // Add a 'uid' condition.
-    $query->condition('aws_cloud_security_group.uid', $account->id());
-  }
-
+  \Drupal::service(AwsCloudHooks::class)->queryAwsCloudSecurityGroupViewsAccessAlter($query);
 }
 
 /**
  * Implements hook_query_TAG_Alter().
  */
+#[LegacyHook]
 function aws_cloud_query_aws_cloud_elastic_ip_views_access_alter(AlterableInterface $query): void {
-  if (!$account = $query->getMetaData('account')) {
-    $account = \Drupal::currentUser();
-  }
-  if ($account->hasPermission('view any aws cloud elastic ip')) {
-    return;
-  }
-  else {
-    // Add a 'uid' condition.
-    $query->condition('aws_cloud_elastic_ip.uid', $account->id());
-  }
-
+  \Drupal::service(AwsCloudHooks::class)->queryAwsCloudElasticIpViewsAccessAlter($query);
 }
 
 /**
  * Implements hook_query_TAG_Alter().
  */
+#[LegacyHook]
 function aws_cloud_query_aws_cloud_key_pair_views_access_alter(AlterableInterface $query): void {
-  if (!$account = $query->getMetaData('account')) {
-    $account = \Drupal::currentUser();
-  }
-  if ($account->hasPermission('view any aws cloud key pair')) {
-    return;
-  }
-  else {
-    // Add a 'uid' condition.
-    $query->condition('aws_cloud_key_pair.uid', $account->id());
-  }
-
+  \Drupal::service(AwsCloudHooks::class)->queryAwsCloudKeyPairViewsAccessAlter($query);
 }
 
 /**
  * Implements hook_query_TAG_Alter().
  */
+#[LegacyHook]
 function aws_cloud_query_aws_cloud_network_interface_views_access_alter(AlterableInterface $query): void {
-  if (!$account = $query->getMetaData('account')) {
-    $account = \Drupal::currentUser();
-  }
-  if ($account->hasPermission('view any aws cloud network interface')) {
-    return;
-  }
-  else {
-    // Add a 'uid' condition.
-    $query->condition('aws_cloud_network_interface.uid', $account->id());
-  }
-
+  \Drupal::service(AwsCloudHooks::class)->queryAwsCloudNetworkInterfaceViewsAccessAlter($query);
 }
 
 /**
  * Implements hook_query_TAG_Alter().
  */
+#[LegacyHook]
 function aws_cloud_query_aws_cloud_snapshot_views_access_alter(AlterableInterface $query): void {
-  if (!$account = $query->getMetaData('account')) {
-    $account = \Drupal::currentUser();
-  }
-  if ($account->hasPermission('view any aws cloud snapshot')) {
-    return;
-  }
-  else {
-    // Add a 'uid' condition.
-    $query->condition('aws_cloud_snapshot.uid', $account->id());
-  }
-
+  \Drupal::service(AwsCloudHooks::class)->queryAwsCloudSnapshotViewsAccessAlter($query);
 }
 
 /**
  * Implements hook_query_TAG_Alter().
  */
+#[LegacyHook]
 function aws_cloud_query_aws_cloud_volume_views_access_alter(AlterableInterface $query): void {
-  if (!$account = $query->getMetaData('account')) {
-    $account = \Drupal::currentUser();
-  }
-  if ($account->hasPermission('view any aws cloud volume')) {
-    return;
-  }
-  else {
-    // Add a 'uid' condition.
-    $query->condition('aws_cloud_volume.uid', $account->id());
-  }
-
+  \Drupal::service(AwsCloudHooks::class)->queryAwsCloudVolumeViewsAccessAlter($query);
 }
 
 /**
  * Implements hook_query_TAG_Alter().
  */
+#[LegacyHook]
 function aws_cloud_query_aws_cloud_vpc_views_access_alter(AlterableInterface $query): void {
-  if (!$account = $query->getMetaData('account')) {
-    $account = \Drupal::currentUser();
-  }
-  if ($account->hasPermission('view any aws cloud vpc')) {
-    return;
-  }
-  else {
-    // Add a 'uid' condition.
-    $query->condition('aws_cloud_vpc.uid', $account->id());
-  }
-
+  \Drupal::service(AwsCloudHooks::class)->queryAwsCloudVpcViewsAccessAlter($query);
 }
 
 /**
  * Implements hook_query_TAG_Alter().
  */
+#[LegacyHook]
 function aws_cloud_query_aws_cloud_subnet_views_access_alter(AlterableInterface $query): void {
-  if (!$account = $query->getMetaData('account')) {
-    $account = \Drupal::currentUser();
-  }
-  if ($account->hasPermission('view any aws cloud subnet')) {
-    return;
-  }
-  else {
-    // Add a 'uid' condition.
-    $query->condition('aws_cloud_subnet.uid', $account->id());
-  }
-
+  \Drupal::service(AwsCloudHooks::class)->queryAwsCloudSubnetViewsAccessAlter($query);
 }
 
 /**
  * Implements hook_query_TAG_Alter().
  */
+#[LegacyHook]
 function aws_cloud_query_aws_cloud_vpc_peering_connection_views_access_alter(AlterableInterface $query): void {
-  if (!$account = $query->getMetaData('account')) {
-    $account = \Drupal::currentUser();
-  }
-  if ($account->hasPermission('view any aws cloud vpc peering connection')) {
-    return;
-  }
-  else {
-    // Add a 'uid' condition.
-    $query->condition('aws_cloud_vpc_peering_connection.uid', $account->id());
-  }
-
+  \Drupal::service(AwsCloudHooks::class)->queryAwsCloudVpcPeeringConnectionViewsAccessAlter($query);
 }
 
 /**
  * Implements hook_query_TAG_Alter().
  */
+#[LegacyHook]
 function aws_cloud_query_aws_cloud_internet_gateway_views_access_alter(AlterableInterface $query): void {
-  if (!$account = $query->getMetaData('account')) {
-    $account = \Drupal::currentUser();
-  }
-  if ($account->hasPermission('view any aws cloud internet gateway')) {
-    return;
-  }
-  else {
-    // Add a 'uid' condition.
-    $query->condition('aws_cloud_internet_gateway.uid', $account->id());
-  }
-
+  \Drupal::service(AwsCloudHooks::class)->queryAwsCloudInternetGatewayViewsAccessAlter($query);
 }
 
 /**
  * Implements hook_query_TAG_Alter().
  */
+#[LegacyHook]
 function aws_cloud_query_aws_cloud_carrier_gateway_views_access_alter(AlterableInterface $query): void {
-  if (!$account = $query->getMetaData('account')) {
-    $account = \Drupal::currentUser();
-  }
-  if ($account->hasPermission('view any aws cloud carrier gateway')) {
-    return;
-  }
-  else {
-    // Add a 'uid' condition.
-    $query->condition('aws_cloud_carrier_gateway.uid', $account->id());
-  }
+  \Drupal::service(AwsCloudHooks::class)->queryAwsCloudCarrierGatewayViewsAccessAlter($query);
 }
 
 /**
  * Implements hook_query_TAG_Alter().
  */
+#[LegacyHook]
 function aws_cloud_query_aws_cloud_transit_gateway_views_access_alter(AlterableInterface $query): void {
-  if (!$account = $query->getMetaData('account')) {
-    $account = \Drupal::currentUser();
-  }
-  if ($account->hasPermission('view any aws cloud transit gateway')) {
-    return;
-  }
-  else {
-    // Add a 'uid' condition.
-    $query->condition('aws_cloud_transit_gateway.uid', $account->id());
-  }
+  \Drupal::service(AwsCloudHooks::class)->queryAwsCloudTransitGatewayViewsAccessAlter($query);
 }
 
 /**
  * Implements hook_query_TAG_Alter().
  */
+#[LegacyHook]
 function aws_cloud_query_aws_cloud_stack_views_access_alter(AlterableInterface $query): void {
-  if (!$account = $query->getMetaData('account')) {
-    $account = \Drupal::currentUser();
-  }
-  if ($account->hasPermission('view any aws cloud stack')) {
-    return;
-  }
-  else {
-    // Add a 'uid' condition.
-    $query->condition('aws_cloud_stack.uid', $account->id());
-  }
+  \Drupal::service(AwsCloudHooks::class)->queryAwsCloudStackViewsAccessAlter($query);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function aws_cloud_form_cloud_launch_template_aws_cloud_launch_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  /** @var \Drupal\cloud\Entity\CloudLaunchTemplate $cloud_launch_template */
-  $cloud_launch_template = \Drupal::routeMatch()->getParameter('cloud_launch_template');
-  $cloud_context = \Drupal::routeMatch()->getParameter('cloud_context');
-
-  $config = \Drupal::config('aws_cloud.settings');
-  if ($config->get('aws_cloud_instance_type_cost_list')) {
-    $form['cost'] = [
-      '#type' => 'details',
-      '#title' => t('Cost'),
-      '#open' => TRUE,
-    ];
-
-    $price_table_renderer = \Drupal::service('aws_cloud.instance_type_price_table_renderer');
-    $form['cost']['price_table'] = $price_table_renderer->render(
-      $cloud_context,
-      $cloud_launch_template->get('field_instance_type')->value
-    );
-  }
-
-  $form['automation'] = [
-    '#type' => 'details',
-    '#title' => t('Automation'),
-    '#open' => TRUE,
-  ];
-
-  $form['automation']['description'] = $form['description'];
-  unset($form['description']);
-
-  $form['automation']['termination_protection'] = [
-    '#type' => 'checkbox',
-    '#title' => t('Termination protection'),
-    '#description' => t('Enable the termination protection. If enabled, this instance cannot be terminated using the console, API, or CLI until termination protection is disabled.'),
-    '#default_value' => $form_state->getFormObject()->getEntity()->get('field_termination_protection')->value === '1',
-  ];
-
-  $config = \Drupal::config('aws_cloud.settings');
-  $form['automation']['terminate'] = [
-    '#type' => 'checkbox',
-    '#title' => t('Automatically terminate instance'),
-    '#description' => t('Terminate instance automatically.  Specify termination date in the date picker below.'),
-    '#default_value' => $config->get('aws_cloud_instance_terminate'),
-  ];
-
-  // @todo make 30 days configurable
-  $form['automation']['termination_date'] = [
-    '#type' => 'datetime',
-    '#title' => t('Termination Date'),
-    '#description' => t('The default termination date is 30 days into the future.'),
-    '#default_value' => DrupalDateTime::createFromTimestamp(time() + 2592000),
-  ];
-
-  /** @var \Drupal\cloud\Entity\CloudLaunchTemplate $cloud_launch_template */
-  $cloud_launch_template = \Drupal::routeMatch()->getParameter('cloud_launch_template');
-
-  $form['automation']['schedule'] = [
-    '#title' => t('Schedule'),
-    '#type' => 'select',
-    '#default_value' => $cloud_launch_template->get('field_schedule')->value,
-    '#options' => aws_cloud_get_schedule(),
-    '#description' => t('Configure start and stop schedule. This helps reduce server hosting costs.'),
-  ];
-  if ($cloud_launch_template->get('field_instance_shutdown_behavior')->value === 'terminate') {
-    // Add a warning message setting a schedule will terminate the instance,
-    // since the shutdown behavior is equal to 'terminate'.
-    $form['automation']['terminate_message'] = [
-      '#markup' => t('Setting a schedule will potentially terminate the instance since the <strong>%text</strong> is set to Terminate',
-        ['%text' => 'Instance shutdown behavior']),
-    ];
-  }
-
-  $cloud_config_plugin = \Drupal::service('plugin.manager.cloud_config_plugin');
-  $cloud_config_plugin->setCloudContext($cloud_context);
-  $cloud_config = $cloud_config_plugin->loadConfigEntity();
-  $form['automation']['automatically_assign_vpc'] = [
-    '#type' => 'checkbox',
-    '#title' => t('Automatically assign a VPC'),
-    '#description' => t('Assign a VPC automatically.'),
-    '#default_value' => $cloud_config->get('field_automatically_assign_vpc')->value,
-  ];
-
-  $form['bastion'] = [
-    '#type' => 'details',
-    '#title' => t('Bastion'),
-    '#open' => TRUE,
-  ];
-
-  $form['bastion']['as_bastion'] = [
-    '#type' => 'checkbox',
-    '#title' => t('As a bastion instance'),
-    '#description' => t('Launch as a bastion instance.'),
-    '#default_value' => FALSE,
-    '#states' => [
-      'visible' => [
-        ':input[name="bastion_instance"]' => ['value' => ''],
-      ],
-    ],
-  ];
-
-  $bastion_instance_options = aws_cloud_get_bastion_instance_options($cloud_context);
-  if (!empty($bastion_instance_options)) {
-    $form['bastion']['bastion_instance'] = [
-      '#type' => 'select',
-      '#title' => t('Use a bastion instance'),
-      '#description' => t("Make a connection from the bastion instance's VPC automatically."),
-      '#options' => $bastion_instance_options,
-      '#empty_value' => '',
-      '#empty_option'  => t('None'),
-      '#states' => [
-        'invisible' => [
-          ':input[name="as_bastion"]' => ['checked' => TRUE],
-        ],
-      ],
-    ];
-  }
-
-  $view_builder = \Drupal::entityTypeManager()->getViewBuilder('cloud_launch_template');
-  $build = $view_builder->view($cloud_launch_template, 'view');
-  unset($build['#weight']);
-  $build['#pre_render'][] = '\Drupal\aws_cloud\Entity\Ec2\AwsCloudViewBuilder::reorderLaunchTemplate';
-  $form['detail'] = $build;
-
-  $form['#validate'][] = 'aws_cloud_form_cloud_launch_template_aws_cloud_launch_form_validate';
-  $form['actions']['submit']['#submit'][1] = 'aws_cloud_form_cloud_launch_template_aws_cloud_launch_form_submit';
+  \Drupal::service(AwsCloudHooks::class)->formCloudLaunchTemplateAwsCloudLaunchFormAlter($form, $form_state, $form_id);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function aws_cloud_form_cloud_launch_template_aws_cloud_approve_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  $form['actions']['submit']['#submit'][1] = 'aws_cloud_form_cloud_launch_template_aws_cloud_approve_form_submit';
+  \Drupal::service(AwsCloudHooks::class)->formCloudLaunchTemplateAwsCloudApproveFormAlter($form, $form_state, $form_id);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function aws_cloud_form_cloud_launch_template_aws_cloud_review_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  $form['actions']['submit']['#submit'][1] = 'aws_cloud_form_cloud_launch_template_aws_cloud_review_form_submit';
+  \Drupal::service(AwsCloudHooks::class)->formCloudLaunchTemplateAwsCloudReviewFormAlter($form, $form_state, $form_id);
 }
 
 /**
@@ -1666,71 +1031,41 @@ function aws_cloud_form_cloud_launch_template_aws_cloud_launch_form_validate(arr
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function aws_cloud_form_cloud_launch_template_aws_cloud_edit_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  aws_cloud_form_cloud_launch_template_aws_cloud_form_common_alter($form, $form_state, $form_id);
-
-  // Hide new revision checkbox.
-  $form['new_revision']['#access'] = FALSE;
-
-  // Disable name field.
-  $form['instance']['name']['#disabled'] = TRUE;
-
-  // Overwrite function ::save.
-  $form['actions']['submit']['#submit'][1] = 'aws_cloud_form_cloud_launch_template_aws_cloud_edit_form_submit';
+  \Drupal::service(AwsCloudHooks::class)->formCloudLaunchTemplateAwsCloudEditFormAlter($form, $form_state, $form_id);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function aws_cloud_form_cloud_launch_template_aws_cloud_add_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  aws_cloud_form_cloud_launch_template_aws_cloud_form_common_alter($form, $form_state, $form_id);
-
-  // Overwrite function ::save.
-  $form['actions']['submit']['#submit'][1] = 'aws_cloud_form_cloud_launch_template_aws_cloud_add_form_submit';
+  \Drupal::service(AwsCloudHooks::class)->formCloudLaunchTemplateAwsCloudAddFormAlter($form, $form_state, $form_id);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function aws_cloud_form_cloud_launch_template_revision_delete_confirm_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  $form['#submit'] = ['aws_cloud_form_cloud_launch_template_revision_delete_confirm_submit'];
+  \Drupal::service(AwsCloudHooks::class)->formCloudLaunchTemplateRevisionDeleteConfirmAlter($form, $form_state, $form_id);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function aws_cloud_form_cloud_launch_template_revision_revert_confirm_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  $form['#submit'] = ['aws_cloud_form_cloud_launch_template_revision_revert_confirm_submit'];
+  \Drupal::service(AwsCloudHooks::class)->formCloudLaunchTemplateRevisionRevertConfirmAlter($form, $form_state, $form_id);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function aws_cloud_form_cloud_launch_template_aws_cloud_copy_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  aws_cloud_form_cloud_launch_template_aws_cloud_form_common_alter($form, $form_state, $form_id);
-
-  // Change name for copy.
-  $name = $form['instance']['name']['widget'][0]['value']['#default_value'];
-  $form['instance']['name']['widget'][0]['value']['#default_value'] = t('copy_of_@name',
-    [
-      '@name' => $name,
-    ]);
-
-  // Hide new revision checkbox.
-  $form['new_revision']['#access'] = FALSE;
-
-  // Clear the revision log message.
-  $form['others']['revision_log_message']['widget'][0]['value']['#default_value'] = NULL;
-
-  // Change value of the submit button.
-  $form['actions']['submit']['#value'] = t('Copy');
-
-  // Delete the delete button.
-  $form['actions']['delete']['#access'] = FALSE;
-
-  // Overwrite function ::save.
-  $form['actions']['submit']['#submit'][1] = 'aws_cloud_form_cloud_launch_template_aws_cloud_copy_form_submit';
-
+  \Drupal::service(AwsCloudHooks::class)->formCloudLaunchTemplateAwsCloudCopyFormAlter($form, $form_state, $form_id);
 }
 
 /**
@@ -2503,8 +1838,9 @@ function aws_cloud_launch_template_edit_is_changed(CloudLaunchTemplate $server_t
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function aws_cloud_form_cloud_launch_template_aws_cloud_delete_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  $form['actions']['submit']['#submit'] = ['aws_cloud_form_cloud_launch_template_aws_cloud_delete_form_submit'];
+  \Drupal::service(AwsCloudHooks::class)->formCloudLaunchTemplateAwsCloudDeleteFormAlter($form, $form_state, $form_id);
 }
 
 /**
@@ -2553,8 +1889,9 @@ function _aws_cloud_delete_server_template($cloud_config): void {
  *
  * Alter form cloud_config_aws_cloud_delete_form.
  */
+#[LegacyHook]
 function aws_cloud_form_cloud_config_aws_cloud_delete_form_alter(&$form, FormStateInterface $form_state, $form_id): void {
-  $form['actions']['submit']['#submit'] = ['aws_cloud_form_cloud_config_aws_cloud_delete_form_submit'];
+  \Drupal::service(AwsCloudHooks::class)->formCloudConfigAwsCloudDeleteFormAlter($form, $form_state, $form_id);
 }
 
 /**
@@ -2585,8 +1922,9 @@ function aws_cloud_form_cloud_config_aws_cloud_delete_form_submit(array $form, F
  *
  * Alter form cloud_config_delete_multiple_confirm_form.
  */
+#[LegacyHook]
 function aws_cloud_form_cloud_config_delete_multiple_confirm_form_alter(&$form, FormStateInterface $form_state, $form_id): void {
-  $form['actions']['submit']['#submit'] = ['aws_cloud_form_cloud_config_delete_multiple_confirm_form_submit'];
+  \Drupal::service(AwsCloudHooks::class)->formCloudConfigDeleteMultipleConfirmFormAlter($form, $form_state, $form_id);
 }
 
 /**
@@ -2621,14 +1959,9 @@ function aws_cloud_form_cloud_config_delete_multiple_confirm_form_submit(array $
 /**
  * Implements hook_preprocess_field_multiple_value_form().
  */
+#[LegacyHook]
 function aws_cloud_preprocess_field_multiple_value_form(array &$variables): void {
-  // Disable reordering of the ip_permission field.
-  if ($variables['element']['#field_name'] === 'ip_permission' || $variables['element']['#field_name'] === 'outbound_permission') {
-    aws_cloud_remove_table_reordering($variables['table']);
-    // Switch the text of "Add another item" to "Add Rule".
-    $variables['element']['add_more']['#value'] = t('Add Rule');
-    $variables['button']['#value'] = t('Add Rule');
-  }
+  \Drupal::service(AwsCloudHooks::class)->preprocessFieldMultipleValueForm($variables);
 }
 
 /**
@@ -2789,93 +2122,17 @@ function aws_cloud_launch_template_field_orders($include_name = TRUE): array {
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function aws_cloud_form_cloud_config_aws_cloud_edit_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  $form['#validate'][] = 'aws_cloud_form_cloud_config_aws_cloud_credentials_validate';
-  $form['#validate'][] = 'aws_cloud_form_cloud_config_aws_cloud_check_iam_permissions_validate';
-
-  $max_region_code_length = CloudConfigInterface::MAX_NAME_LENGTH;
-  $form['name']['widget'][0]['value']['#description'] = t('The name of the cloud service provider. The maximum number of characters allowed in the name is %max_region_code_length.', [
-    '%max_region_code_length' => $max_region_code_length,
-  ]);
-  $form['name']['widget'][0]['value']['#maxlength'] = $max_region_code_length;
-
-  aws_cloud_form_cloud_config_aws_cloud_form_common_alter($form, $form_state, $form_id);
-
-  // Location fieldset is unnecessary for edit form.
-  unset($form['profile']['location']);
+  \Drupal::service(AwsCloudHooks::class)->formCloudConfigAwsCloudEditFormAlter($form, $form_state, $form_id);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function aws_cloud_form_cloud_config_aws_cloud_add_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  // Hide fields which will be set automatically.
-  $form['cloud_context']['#access'] = FALSE;
-  $form['field_region']['#access'] = FALSE;
-  $form['field_api_endpoint_uri']['#access'] = FALSE;
-  $form['field_image_upload_url']['#access'] = FALSE;
-  $form['field_system_vpc']['#access'] = FALSE;
-  $form['field_location_country']['#access'] = FALSE;
-  $form['field_location_city']['#access'] = FALSE;
-  $form['field_location_longitude']['#access'] = FALSE;
-  $form['field_location_latitude']['#access'] = FALSE;
-  // Hide the field_check_iam_permissions from the add form.
-  $form['field_check_iam_permissions']['#access'] = FALSE;
-
-  $form['actions']['submit']['#submit'] = ['aws_cloud_form_cloud_config_aws_cloud_add_form_submit'];
-  $form['#validate'][] = 'aws_cloud_form_cloud_config_aws_cloud_add_form_validate';
-  $form['#validate'][] = 'aws_cloud_form_cloud_config_aws_cloud_credentials_validate';
-  $form['#validate'][] = 'aws_cloud_form_cloud_config_aws_cloud_check_iam_permissions_validate';
-  // Get all regions.
-  $regions = \Drupal::service('aws_cloud.ec2')->getRegions();
-  $region_names = array_values(preg_replace('/^.*(\(.*\))/', '$1', $regions));
-  // Get the <max length of region code> + 1 (one white space)
-  $max_region_name = 0;
-  foreach ($region_names ?: [] as $region_name) {
-    $max_region_name = max($max_region_name, strlen($region_name ?? ''));
-  }
-  $max_region_code_length = CloudConfigInterface::MAX_NAME_LENGTH - ($max_region_name + 1);
-  $max_name_length = CloudConfigInterface::MAX_NAME_LENGTH;
-  $form['name']['widget'][0]['value']['#description'] = t('Enter the name of the cloud service provider. The region name will be automatically added. The maximum number of characters of the name including the region name is %max_name_length.', [
-    '%max_name_length' => $max_name_length,
-  ]);
-  $form['name']['widget'][0]['value']['#maxlength'] = $max_region_code_length;
-
-  aws_cloud_form_cloud_config_aws_cloud_form_common_alter($form, $form_state, $form_id);
-
-  // VPC fieldset is unnecessary for add form.
-  unset($form['vpc']);
-
-  // Location fieldset is unnecessary for add form.
-  unset($form['profile']['location']);
-
-  // Add checkboxes to enable regions.
-  $ec2_service = \Drupal::service('aws_cloud.ec2');
-  $regions = $ec2_service->getRegions();
-  $form['profile']['common']['regions'] = [
-    '#title' => t('Regions'),
-    '#description' => t('Select regions to create cloud service providers.'),
-    '#type' => 'select',
-    '#options' => $regions,
-    '#required' => TRUE,
-    '#multiple' => TRUE,
-    '#size' => count($regions),
-    '#weight' => $form['profile']['common']['field_account_id']['#weight'] + 1,
-  ];
-  // Add radios asking user for iam validation preference.
-  $form['profile']['common']['iam_validation_options'] = [
-    '#title' => t('Validate IAM permissions'),
-    '#description' => t('Validate that the AWS account ID has all the necessary IAM permissions.'),
-    '#type' => 'radios',
-    '#options' => [
-      Ec2ServiceInterface::IAM_VALIDATION_NO_CHECK => t('Do not validate IAM permissions'),
-      Ec2ServiceInterface::IAM_VALIDATION_ONE_REGION => t('Validate one region'),
-      Ec2ServiceInterface::IAM_VALIDATION_ALL_REGIONS => t('Validate all regions'),
-    ],
-    '#default_value' => Ec2ServiceInterface::IAM_VALIDATION_ONE_REGION,
-    '#weight' => $form['profile']['common']['regions']['#weight'] + 1,
-    '#required' => TRUE,
-  ];
+  \Drupal::service(AwsCloudHooks::class)->formCloudConfigAwsCloudAddFormAlter($form, $form_state, $form_id);
 }
 
 /**
@@ -5104,39 +4361,17 @@ function aws_cloud_get_available_elastic_ips($cloud_context) {
 /**
  * Implements hook_entity_view_alter().
  */
+#[LegacyHook]
 function aws_cloud_entity_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display): void {
-  if ($entity->getEntityTypeId() === 'cloud_launch_template'
-  && $entity->bundle() === 'aws_cloud') {
-    \Drupal::service('cloud')->reorderForm($build, aws_cloud_launch_template_field_orders(FALSE));
-    $build['#attached']['library'][] = 'aws_cloud/aws_cloud_view_builder';
-  }
-  if ($entity->getEntityTypeId() === 'aws_cloud_image' && !empty($build['visibility'][0]['#markup'])) {
-    if ($build['visibility'][0]['#markup']->__toString() === 'Off') {
-      $build['visibility'][0]['#markup'] = 'Private';
-    }
-    else {
-      $build['visibility'][0]['#markup'] = 'Public';
-    }
-  }
+  \Drupal::service(AwsCloudHooks::class)->entityViewAlter($build, $entity, $display);
 }
 
 /**
  * Implements hook_page_attachments().
  */
+#[LegacyHook]
 function aws_cloud_page_attachments(array &$attachments): void {
-  $route = \Drupal::routeMatch();
-  if (in_array($route->getRouteName(), [
-    'view.aws_cloud_instance.list',
-    'view.aws_cloud_image.list',
-    'view.aws_cloud_snapshot.list',
-    'view.aws_cloud_volume.list',
-  ])) {
-    $attachments['#attached']['library'][] = 'aws_cloud/aws_cloud_auto_refresh';
-
-    $config = \Drupal::config('aws_cloud.settings');
-    $attachments['#attached']['drupalSettings']['aws_cloud_view_refresh_interval']
-      = $config->get('aws_cloud_view_refresh_interval');
-  }
+  \Drupal::service(AwsCloudHooks::class)->pageAttachments($attachments);
 }
 
 /**
@@ -5181,60 +4416,33 @@ function aws_cloud_get_reimport_interval(): int {
 /**
  * Implements hook_views_pre_view().
  */
+#[LegacyHook]
 function aws_cloud_views_pre_view($view, $display_id, array &$args): void {
-  if ($view->id() === 'cloud_config') {
-    $config = \Drupal::config('aws_cloud.settings');
-    if (empty($config->get('aws_cloud_instance_type_prices'))) {
-      $view->removeHandler($display_id, 'field', 'pricing_internal_cloud_config');
-    }
-
-    if (empty($config->get('aws_cloud_instance_type_prices_spreadsheet'))) {
-      $view->removeHandler($display_id, 'field', 'pricing_external_cloud_config');
-    }
-
-    return;
-  }
-
-  if ($view->id() === 'aws_cloud_instance') {
-    $config = \Drupal::config('aws_cloud.settings');
-    if (empty($config->get('aws_cloud_instance_list_cost_column'))) {
-      $view->removeHandler($display_id, 'field', 'cost');
-    }
-  }
+  \Drupal::service(AwsCloudHooks::class)->viewsPreView($view, $display_id, $args);
 }
 
 /**
  * Implements hook_menu_local_tasks_alter().
  */
+#[LegacyHook]
 function aws_cloud_menu_local_tasks_alter(array &$data, $route_name): void {
-  $config = \Drupal::config('aws_cloud.settings');
-  if (empty($config->get('aws_cloud_instance_type_prices'))) {
-    unset($data['tabs'][0]['aws_cloud.local_tasks.instance_type_price']);
-  }
+  \Drupal::service(AwsCloudHooks::class)->menuLocalTasksAlter($data, $route_name);
 }
 
 /**
  * Implements hook_menu_local_actions_alter().
  */
+#[LegacyHook]
 function aws_cloud_menu_local_actions_alter(array &$local_actions): void {
-  $config = \Drupal::config('aws_cloud.settings');
-  if (empty($config->get('aws_cloud_instance_type_prices'))) {
-    unset($local_actions['aws_cloud.instance_type_prices']);
-  }
+  \Drupal::service(AwsCloudHooks::class)->menuLocalActionsAlter($local_actions);
 }
 
 /**
  * Implements hook_form_alter().
  */
+#[LegacyHook]
 function aws_cloud_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  if (strpos($form_id, 'views_form_aws_cloud_') === 0) {
-    $form['#submit'][] = 'cloud_views_bulk_form_submit';
-  }
-
-  if ($form['#id'] === 'views-exposed-form-aws-cloud-image-list') {
-    $form['visibility']['#options'][1] = t('Public');
-    $form['visibility']['#options'][0] = t('Private');
-  }
+  \Drupal::service(AwsCloudHooks::class)->formAlter($form, $form_state, $form_id);
 }
 
 /**
@@ -5401,37 +4609,9 @@ function aws_cloud_delete_flow_log($cloud_context, $vpc_id): void {
 /**
  * Implements hook_ENTITY_TYPE_view_alter().
  */
+#[LegacyHook]
 function aws_cloud_cloud_config_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display): void {
-  if ($entity->bundle() === 'aws_cloud') {
-    $map_json_url = \Drupal::config('cloud.settings')->get('cloud_custom_location_map_json_url');
-    $url = Url::fromRoute('entity.cloud_config.location', ['cloud_config' => $entity->id()])->toString();
-
-    $build['cloud_config_location_map'] = [
-      '#markup' => '<div id="cloud_config_location"></div>',
-      '#attached' => [
-        'library' => [
-          'cloud/cloud_config_location',
-        ],
-        'drupalSettings' => [
-          'cloud' => [
-            'cloud_location_map_json_url' => $map_json_url,
-            'cloud_config_location_json_url' => $url,
-          ],
-        ],
-      ],
-    ];
-
-    $build['field_location_country']['#access'] = FALSE;
-    $build['field_location_city']['#access'] = FALSE;
-    $build['field_location_longitude']['#access'] = FALSE;
-    $build['field_location_latitude']['#access'] = FALSE;
-
-    aws_cloud_cloud_config_fieldsets($build);
-
-    $weight = $build['profile']['common']['#weight'];
-    $build['profile']['location']['#weight'] = --$weight;
-
-  }
+  \Drupal::service(AwsCloudHooks::class)->cloudConfigViewAlter($build, $entity, $display);
 }
 
 /**
@@ -5605,44 +4785,9 @@ function aws_cloud_cloud_config_fieldsets(array &$fields): void {
 /**
  * Implements hook_query_TAG_Alter().
  */
+#[LegacyHook]
 function aws_cloud_query_all_aws_cloud_resource_views_access_alter(AlterableInterface $query): void {
-  // Get table name.
-  $query_tables = $query->getTables();
-
-  foreach ($query_tables ?: [] as $info) {
-    if ($info['table'] instanceof SelectInterface) {
-      // @FIXME The single `continue` statement doesn't make sense below.
-      continue;
-    }
-  }
-
-  if (empty($info['table'])) {
-    return;
-  }
-  $table_name = $info['table'];
-
-  if (!$account = $query->getMetaData('account')) {
-    $account = \Drupal::currentUser();
-  }
-
-  $entities = \Drupal::service('plugin.manager.cloud_config_plugin')->loadConfigEntities('aws_cloud');
-  $cloud_contexts = [];
-  foreach ($entities ?: [] as $entity) {
-    if ($account->hasPermission('view all cloud service providers')
-    || $account->hasPermission("view {$entity->getCloudContext()}")) {
-      $cloud_contexts[] = $entity->getCloudContext();
-    }
-  }
-
-  if (count($cloud_contexts)) {
-    $query->condition("{$table_name}.cloud_context", $cloud_contexts, 'IN');
-  }
-  else {
-    // No permissions, do not let them view any cloud context.
-    // Return an empty page.  This is just a catch-all.  In
-    // normal cases, users will have access to certain cloud context.
-    $query->condition("{$table_name}.cloud_context", '');
-  }
+  \Drupal::service(AwsCloudHooks::class)->queryAllAwsCloudResourceViewsAccessAlter($query);
 }
 
 /**
diff --git a/modules/cloud_service_providers/aws_cloud/aws_cloud.services.yml b/modules/cloud_service_providers/aws_cloud/aws_cloud.services.yml
index 7778185..5845222 100644
--- a/modules/cloud_service_providers/aws_cloud/aws_cloud.services.yml
+++ b/modules/cloud_service_providers/aws_cloud/aws_cloud.services.yml
@@ -72,3 +72,11 @@ services:
   aws_cloud.operations:
     class: Drupal\aws_cloud\Service\AwsCloud\AwsCloudOperationsService
     arguments: ['@entity_type.manager', '@plugin.manager.cloud_config_plugin', '@cloud', '@aws_cloud.ec2', '@entity.link_renderer', '@config.factory', '@current_user', '@module_handler', '@datetime.time']
+
+  Drupal\aws_cloud\Hook\AwsCloudHooks:
+    class: Drupal\aws_cloud\Hook\AwsCloudHooks
+    autowire: true
+
+  Drupal\aws_cloud\Hook\AwsCloudTokensHooks:
+    class: Drupal\aws_cloud\Hook\AwsCloudTokensHooks
+    autowire: true
diff --git a/modules/cloud_service_providers/aws_cloud/aws_cloud.tokens.inc b/modules/cloud_service_providers/aws_cloud/aws_cloud.tokens.inc
index 2847e2b..6063abe 100644
--- a/modules/cloud_service_providers/aws_cloud/aws_cloud.tokens.inc
+++ b/modules/cloud_service_providers/aws_cloud/aws_cloud.tokens.inc
@@ -5,290 +5,24 @@
  * Builds placeholder replacement tokens for aws-related data.
  */
 
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\aws_cloud\Hook\AwsCloudTokensHooks;
 use Drupal\Core\Render\BubbleableMetadata;
 
 /**
  * Implements hook_token_info().
  */
+#[LegacyHook]
 function aws_cloud_token_info(): array {
-  $types['aws_cloud_instance'] = [
-    'name' => t('EC2 instances'),
-    'description' => t('Tokens related to individual EC2 instances.'),
-    'needs-data' => 'aws_cloud_instance',
-  ];
-  $instance['name'] = [
-    'name' => t('EC2 instance name'),
-    'description' => t('The name of the EC2 instance entity.'),
-  ];
-  $instance['id'] = [
-    'name' => t('EC2 instance ID'),
-    'description' => t('EC2 instance ID.'),
-  ];
-  $instance['launch_time'] = [
-    'name' => t('Launch time'),
-    'description' => t('The time the EC2 instance launched.'),
-  ];
-  $instance['instance_state'] = [
-    'name' => t('Instance state'),
-    'description' => t('The current state of the instance.'),
-  ];
-  $instance['availability_zone'] = [
-    'name' => t('Availability Zone'),
-    'description' => t('Zone the instance is in.'),
-  ];
-  $instance['private_ip'] = [
-    'name' => t('Private IP address'),
-    'description' => t('The EC2 instance private IP.'),
-  ];
-  $instance['public_ip'] = [
-    'name' => t('Public IP address'),
-    'description' => t('The EC2 instance public IP.'),
-  ];
-  $instance['elastic_ip'] = [
-    'name' => t('Elastic IP address'),
-    'description' => t('The instance Elastic IP address.'),
-  ];
-  $instance['instance_link'] = [
-    'name' => t('EC2 instance Link'),
-    'description' => t('Absolute link to the EC2 instance.'),
-  ];
-  $instance['instance_edit_link'] = [
-    'name' => t('Edit EC2 instance link'),
-    'description' => t('Absolute link to edit the EC2 instance.'),
-  ];
-
-  $types['aws_cloud_volume'] = [
-    'name' => t('EBS volume'),
-    'description' => t('Tokens related to individual EBS volumes.'),
-    'needs-data' => 'aws_cloud_volume',
-  ];
-  $volume['name'] = [
-    'name' => t('EBS volume name'),
-    'description' => t('The name of the EBS volume entity.'),
-  ];
-  $volume['volume_link'] = [
-    'name' => t('EBS volume Link'),
-    'description' => t('Absolute link to EBS volume.'),
-  ];
-  $volume['volume_edit_link'] = [
-    'name' => t('EBS volume edit link'),
-    'description' => t('Absolute link to edit the EBS volume.'),
-  ];
-  $volume['created'] = [
-    'name' => t('Create date'),
-    'description' => t('The EBS volume create date.'),
-  ];
-
-  $types['aws_cloud_snapshot'] = [
-    'name' => t('EBS snapshot'),
-    'description' => t('Tokens related to individual EBS snapshots.'),
-    'needs-data' => 'aws_cloud_snapshot',
-  ];
-  $snapshot['name'] = [
-    'name' => t('EBS snapshot name'),
-    'description' => t('The name of the EBS snapshot entity.'),
-  ];
-  $snapshot['snapshot_link'] = [
-    'name' => t('EBS snapshot Link'),
-    'description' => t('Absolute link to EBS snapshot.'),
-  ];
-  $snapshot['snapshot_edit_link'] = [
-    'name' => t('EBS snapshot Edit Link'),
-    'description' => t('Absolute link to edit the EBS snapshot.'),
-  ];
-  $snapshot['created'] = [
-    'name' => t('Create date'),
-    'description' => t('The EBS snapshot create date.'),
-  ];
-
-  $types['aws_cloud_elastic_ip'] = [
-    'name' => t('Elastic IP'),
-    'description' => t('Tokens related to individual Elastic IPs.'),
-    'needs-data' => 'aws_cloud_elastic_ip',
-  ];
-  $elastic_ip['name'] = [
-    'name' => t('Elastic IP name'),
-    'description' => t('The name of the Elastic IP entity.'),
-  ];
-  $elastic_ip['elastic_ip_link'] = [
-    'name' => t('Elastic IP Link'),
-    'description' => t('Absolute link to Elastic IP.'),
-  ];
-  $elastic_ip['elastic_ip_edit_link'] = [
-    'name' => t('Elastic IP Edit Link'),
-    'description' => t('Absolute link to edit the Elastic IP.'),
-  ];
-  $elastic_ip['created'] = [
-    'name' => t('Create date'),
-    'description' => t('The Elastic IP create date.'),
-  ];
-  $types['aws_cloud_launch_template'] = [
-    'name' => t('Launch Template'),
-    'description' => t('Tokens related to individual launch template.'),
-    'needs-data' => 'aws_cloud_launch_template',
-  ];
-  $launch_template['name'] = [
-    'name' => t('List of launch template name'),
-    'description' => t('Enter the name of the launch template entity.'),
-  ];
-  $launch_template['launch_template_link'] = [
-    'name' => t('Launch template link'),
-    'description' => t('An absolute link to launch template.'),
-  ];
-  $launch_template['launch_template_edit_link'] = [
-    'name' => t('Launch template edit link'),
-    'description' => t('An absolute link to edit the launch template.'),
-  ];
-  $launch_template['changed'] = [
-    'name' => t('Change date'),
-    'description' => t('The launch template change date.'),
-  ];
-  $types['aws_cloud_instance_email'] = [
-    'name' => t('EC2 instance email'),
-    'description' => t('Tokens related to individual EC2 instance email.'),
-    'needs-data' => 'aws_cloud_instance_email',
-  ];
-  $instance_email['instances'] = [
-    'name' => t('List of EC2 instances'),
-    'description' => t('List of EC2 instances to display to user.'),
-  ];
-
-  $types['aws_cloud_volume_email'] = [
-    'name' => t('EBS volume email'),
-    'description' => t('Tokens related to individual EBS volume email.'),
-    'needs-data' => 'aws_cloud_volume_email',
-  ];
-  $volume_email['volumes'] = [
-    'name' => t('List of EBS volumes'),
-    'description' => t('List of EBS volumes to display to user.'),
-  ];
-
-  $types['aws_cloud_snapshot_email'] = [
-    'name' => t('EBS snapshot email'),
-    'description' => t('Tokens related to individual EBS snapshot email.'),
-    'needs-data' => 'aws_cloud_snapshot_email',
-  ];
-  $snapshot_email['snapshots'] = [
-    'name' => t('List of EBS snapshots'),
-    'description' => t('List of EBS snapshots to display to user.'),
-  ];
-
-  $types['aws_cloud_elastic_ip_email'] = [
-    'name' => t('Elastic IP email'),
-    'description' => t('Tokens related to individual Elastic IP email.'),
-    'needs-data' => 'aws_cloud_elastic_ip_email',
-  ];
-  $elastic_ip_email['elastic_ips'] = [
-    'name' => t('List of Elastic IPs'),
-    'description' => t('List of Elastic IPs to display to a user.'),
-  ];
-  $types['aws_cloud_launch_template_email'] = [
-    'name' => t('Launch template email'),
-    'description' => t('Tokens related to individual launch template email.'),
-    'needs-data' => 'aws_cloud_launch_template_email',
-  ];
-  $launch_template_email['launch_templates'] = [
-    'name' => t('List of launch templates'),
-    'description' => t('List of launch templates to display to a user.'),
-  ];
-  $types['aws_cloud_launch_template_request_email'] = [
-    'name' => t('Launch template request email'),
-    'description' => t('Tokens related to individual launch template request email.'),
-    'needs-data' => 'aws_cloud_launch_template_request_email',
-  ];
-  $launch_template_request_email['launch_templates_request'] = [
-    'name' => t('List of request launch templates'),
-    'description' => t('List of request launch templates to display to a user.'),
-  ];
-
-  $types['aws_cloud_launch_template_requext'] = [
-    'name' => t('Launch template request'),
-    'description' => t('Tokens related to individual launch template.'),
-    'needs-data' => 'aws_cloud_launch_template_request',
-  ];
-  $launch_template_request['name'] = [
-    'name' => t('List of launch template name'),
-    'description' => t('Enter the name of the launch template entity.'),
-  ];
-  $launch_template_request['launch_template_link'] = [
-    'name' => t('Launch template link'),
-    'description' => t('An absolute link to launch template.'),
-  ];
-  $launch_template_request['launch_template_edit_link'] = [
-    'name' => t('Launch template edit link'),
-    'description' => t('An absolute link to edit the launch template.'),
-  ];
-  $launch_template_request['changed'] = [
-    'name' => t('Change date'),
-    'description' => t('The launch template change date.'),
-  ];
-  $launch_template_request['launch_template_button_approve'] = [
-    'name' => t('Approve button'),
-    'description' => t('The launch template approve button.'),
-  ];
-
-  return [
-    'types' => $types,
-    'tokens' => [
-      'aws_cloud_instance' => $instance,
-      'aws_cloud_volume' => $volume,
-      'aws_cloud_snapshot' => $snapshot,
-      'aws_cloud_elastic_ip' => $elastic_ip,
-      'aws_cloud_launch_template' => $launch_template,
-      'aws_cloud_instance_email' => $instance_email,
-      'aws_cloud_volume_email' => $volume_email,
-      'aws_cloud_snapshot_email' => $snapshot_email,
-      'aws_cloud_elastic_ip_email' => $elastic_ip_email,
-      'aws_cloud_launch_template_email' => $launch_template_email,
-      'aws_cloud_launch_template_request' => $launch_template_request,
-      'aws_cloud_launch_template_request_email' => $launch_template_request_email,
-    ],
-  ];
+  return \Drupal::service(AwsCloudTokensHooks::class)->tokenInfo();
 }
 
 /**
  * Implements hook_tokens().
  */
+#[LegacyHook]
 function aws_cloud_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata): array {
-
-  $replacements = [];
-  if ($type === 'aws_cloud_instance' && !empty($data['aws_cloud_instance'])) {
-    $replacements = aws_cloud_instance_tokens($tokens, $data);
-  }
-  elseif ($type === 'aws_cloud_volume' && !empty($data['aws_cloud_volume'])) {
-    $replacements = aws_cloud_volume_tokens($tokens, $data);
-  }
-  elseif ($type === 'aws_cloud_snapshot' && !empty($data['aws_cloud_snapshot'])) {
-    $replacements = aws_cloud_snapshot_tokens($tokens, $data);
-  }
-  elseif ($type === 'aws_cloud_elastic_ip' && !empty($data['aws_cloud_elastic_ip'])) {
-    $replacements = aws_cloud_elastic_ip_tokens($tokens, $data);
-  }
-  elseif ($type === 'aws_cloud_launch_template' && !empty($data['aws_cloud_launch_template'])) {
-    $replacements = aws_cloud_launch_template_tokens($tokens, $data);
-  }
-  elseif ($type === 'aws_cloud_launch_template_request' && !empty($data['aws_cloud_launch_template_request'])) {
-    $replacements = aws_cloud_launch_template_request_tokens($tokens, $data);
-  }
-  elseif ($type === 'aws_cloud_instance_email') {
-    $replacements = aws_cloud_instance_email_tokens($tokens, $data);
-  }
-  elseif ($type === 'aws_cloud_volume_email') {
-    $replacements = aws_cloud_volume_email_tokens($tokens, $data);
-  }
-  elseif ($type === 'aws_cloud_snapshot_email') {
-    $replacements = aws_cloud_snapshot_email_tokens($tokens, $data);
-  }
-  elseif ($type === 'aws_cloud_elastic_ip_email') {
-    $replacements = aws_cloud_elastic_ip_email_tokens($tokens, $data);
-  }
-  elseif ($type === 'aws_cloud_launch_template_email') {
-    $replacements = aws_cloud_launch_template_email_tokens($tokens, $data);
-  }
-  elseif ($type === 'aws_cloud_launch_template_request_email') {
-    $replacements = aws_cloud_launch_template_request_email_tokens($tokens, $data);
-  }
-  return $replacements;
+  return \Drupal::service(AwsCloudTokensHooks::class)->tokens($type, $tokens, $data, $options, $bubbleable_metadata);
 }
 
 /**
diff --git a/modules/cloud_service_providers/aws_cloud/src/Form/Config/AwsCloudAdminNotificationSettings.php b/modules/cloud_service_providers/aws_cloud/src/Form/Config/AwsCloudAdminNotificationSettings.php
index bf8f3fe..ab55fd9 100644
--- a/modules/cloud_service_providers/aws_cloud/src/Form/Config/AwsCloudAdminNotificationSettings.php
+++ b/modules/cloud_service_providers/aws_cloud/src/Form/Config/AwsCloudAdminNotificationSettings.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\aws_cloud\Form\Config;
 
+use Drupal\Component\Utility\DeprecationHelper;
 use Drupal\Component\Utility\Html;
 use Drupal\Core\Config\Config;
 use Drupal\Core\Config\ConfigFactoryInterface;
@@ -144,9 +145,11 @@ class AwsCloudAdminNotificationSettings extends ConfigFormBase {
       '#default_value' => $config->get('aws_cloud_notification_frequency'),
     ];
 
-    $form['instance']['notification_settings']['aws_cloud_instance_notification_fields'] = [
+    $form['instance']['notification_settings']['aws_cloud_instance_notification_fields'] = DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => [
+      '#type' => 'fieldset',
+    ], fn() => [
       '#type' => 'fieldgroup',
-    ];
+    ]);
 
     $form['instance']['notification_settings']['aws_cloud_instance_notification_fields']['aws_cloud_instance_notification_title'] = [
       '#type' => 'item',
@@ -363,9 +366,11 @@ class AwsCloudAdminNotificationSettings extends ConfigFormBase {
       '#default_value' => $config->get('aws_cloud_unused_volume_criteria'),
     ];
 
-    $form['volume']['notification_settings']['aws_cloud_volume_notification_fields'] = [
+    $form['volume']['notification_settings']['aws_cloud_volume_notification_fields'] = DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => [
+      '#type' => 'fieldset',
+    ], fn() => [
       '#type' => 'fieldgroup',
-    ];
+    ]);
 
     $form['volume']['notification_settings']['aws_cloud_volume_notification_fields']['aws_cloud_volume_notification_title'] = [
       '#type' => 'item',
@@ -470,9 +475,11 @@ class AwsCloudAdminNotificationSettings extends ConfigFormBase {
       '#default_value' => $config->get('aws_cloud_stale_snapshot_criteria'),
     ];
 
-    $form['snapshot']['notification_settings']['aws_cloud_snapshot_notification_fields'] = [
+    $form['snapshot']['notification_settings']['aws_cloud_snapshot_notification_fields'] = DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => [
+      '#type' => 'fieldset',
+    ], fn() => [
       '#type' => 'fieldgroup',
-    ];
+    ]);
 
     $form['snapshot']['notification_settings']['aws_cloud_snapshot_notification_fields']['aws_cloud_snapshot_notification_title'] = [
       '#type' => 'item',
@@ -563,9 +570,11 @@ class AwsCloudAdminNotificationSettings extends ConfigFormBase {
       '#default_value' => $config->get('aws_cloud_elastic_ip_notification_frequency'),
     ];
 
-    $form['elastic_ip']['notification_settings']['aws_cloud_elastic_ip_notification_fields'] = [
+    $form['elastic_ip']['notification_settings']['aws_cloud_elastic_ip_notification_fields'] = DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => [
+      '#type' => 'fieldset',
+    ], fn() => [
       '#type' => 'fieldgroup',
-    ];
+    ]);
 
     $form['elastic_ip']['notification_settings']['aws_cloud_elastic_ip_notification_fields']['aws_cloud_elastic_ip_notification_title'] = [
       '#type' => 'item',
diff --git a/modules/cloud_service_providers/aws_cloud/src/Form/Config/AwsCloudAdminSettings.php b/modules/cloud_service_providers/aws_cloud/src/Form/Config/AwsCloudAdminSettings.php
index 0bcf6ca..7b22758 100644
--- a/modules/cloud_service_providers/aws_cloud/src/Form/Config/AwsCloudAdminSettings.php
+++ b/modules/cloud_service_providers/aws_cloud/src/Form/Config/AwsCloudAdminSettings.php
@@ -395,7 +395,7 @@ class AwsCloudAdminSettings extends ConfigFormBase {
         $views_settings[$key] = (int) $value;
       }
       elseif ($key === 'aws_cloud_view_expose_items_per_page') {
-        $views_settings[$key] = (boolean) $value;
+        $views_settings[$key] = (bool) $value;
       }
 
       if ($key === 'aws_cloud_cloud_config_icon') {
diff --git a/modules/cloud_service_providers/aws_cloud/src/Hook/AwsCloudHooks.php b/modules/cloud_service_providers/aws_cloud/src/Hook/AwsCloudHooks.php
new file mode 100644
index 0000000..f863c9c
--- /dev/null
+++ b/modules/cloud_service_providers/aws_cloud/src/Hook/AwsCloudHooks.php
@@ -0,0 +1,1214 @@
+<?php
+
+namespace Drupal\aws_cloud\Hook;
+
+use Drupal\Core\Database\Query\SelectInterface;
+use Drupal\aws_cloud\Service\Ec2\Ec2ServiceInterface;
+use Drupal\cloud\Entity\CloudConfigInterface;
+use Drupal\Core\Datetime\DrupalDateTime;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Database\Query\Condition;
+use Drupal\Core\Database\Query\AlterableInterface;
+use Drupal\Component\Utility\DeprecationHelper;
+use Drupal\cloud\Entity\CloudConfig;
+use Drupal\Core\Url;
+use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for aws_cloud.
+ */
+class AwsCloudHooks {
+  use StringTranslationTrait;
+
+  /**
+   * Implements hook_help().
+   */
+  #[Hook('help')]
+  public function help($route_name, RouteMatchInterface $route_match): string {
+    $output = '';
+    switch ($route_name) {
+      case 'help.page.aws_cloud':
+        $output = '<h3>' . $this->t('About') . '</h3>';
+        $output .= '<ul>';
+        $output .= '<li>' . \Drupal::service('cloud')->getVersion() . '</li>';
+        $output .= '<li>' . $this->t('AWS Cloud (aws_cloud module) creates a user interface for managing AWS-related clouds. AWS Cloud is defined as Amazon EC2.') . '</li>';
+        $output .= '</ul>';
+        $output .= '<h3>' . $this->t('Features') . '</h3>';
+        $output .= '<ul>';
+        $output .= '<li>' . $this->t('<strong>EC2</strong>');
+        $output .= '<ul>';
+        $output .= '<li>' . $this->t('Manage EC2.') . '</li>';
+        $output .= '<li>' . $this->t('Integrate with AWS instance scheduler.') . '</li>';
+        $output .= '<li>' . $this->t('Support EC2 launch templates.') . '</li>';
+        $output .= '<li>' . $this->t('Support automatic termination scheduling.') . '</li>';
+        $output .= '<li>' . $this->t('Support instance profile (instance IAM role), assume role and switch role') . '</li>';
+        $output .= '</ul></li></ul>';
+        $output .= '<ul>';
+        $output .= '<li>' . $this->t('<strong>VPC</strong>');
+        $output .= '<ul>';
+        $output .= '<li>' . $this->t('Manage VPC.') . '</li>';
+        $output .= '<li>' . $this->t('Create a VPC automatically based on each login user account.') . '</li>';
+        $output .= '<li>' . $this->t('Create and manage bastion for each VPC.') . '</li>';
+        $output .= '<li>' . $this->t('Support VPC flow logs.') . '</li>';
+        $output .= '<li>' . $this->t('Support Cloud Formation templates to deploy a Cloud Orchestrator distribution.') . '</li>';
+        $output .= '</ul></li></ul>';
+        $output .= '<ul>';
+        $output .= '<li>' . $this->t('<strong>Cost</strong>');
+        $output .= '<ul>';
+        $output .= '<li>' . $this->t('List up instance type specs and costs.') . '</li>';
+        $output .= '<li>' . $this->t('Create Google Spreadsheets for instance type specs and costs.') . '</li>';
+        $output .= '<li>' . $this->t('Send reminder alerts for long-running instances, unused volumes and snapshots.') . '</li>';
+        $output .= '<li>' . $this->t('Manage budgets and credits.') . '</li>';
+        $output .= '</ul></li></ul>';
+        $output .= '<p>' . $this->t('For more information, see the <a href=":cloud_documentation">online documentation for the AWS Cloud module</a>.', [
+          ':cloud_documentation' => 'https://www.drupal.org/docs/8/modules/cloud',
+        ]) . '</p>';
+    }
+    return $output;
+  }
+
+  /**
+   * Implements hook_ENTITY_TYPE_predelete().
+   */
+  #[Hook('user_predelete')]
+  public static function userPredelete($account): void {
+    /** @var Drupal\cloud\Service\CloudService $cloud_service */
+    $cloud_service = \Drupal::service('cloud');
+    $entity_types = $cloud_service->getProviderEntityTypes('aws_cloud');
+    foreach ($entity_types ?: [] as $entity_type) {
+      if (empty($entity_type)) {
+        continue;
+      }
+      $ids = \Drupal::entityTypeManager()->getStorage($entity_type->id())->getQuery()->accessCheck(TRUE)->condition('uid', $account->id())->execute();
+      if (count($ids) === 0) {
+        continue;
+      }
+      $cloud_service->reassignUids($ids, [
+        'uid' => 0,
+      ], $entity_type->id(), FALSE, $account, [
+        'aws_cloud_reassign_entity_uid_callback',
+      ]);
+    }
+  }
+
+  /**
+   * Implements hook_entity_view().
+   */
+  #[Hook('entity_view')]
+  public static function entityView(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode): void {
+    if (!empty($entity) && $view_mode === 'full' && $entity->getEntityTypeId() === 'aws_cloud_key_pair' && $entity->id() !== NULL) {
+      /** @var \Drupal\aws_cloud\Entity\Ec2\KeyPairInterface|null $keypair */
+      $keypair = \Drupal::entityTypeManager()->getStorage('aws_cloud_key_pair')->load($entity->id());
+      // If the key is on the server, prompt user to download it.
+      if (!empty($keypair) && !empty($keypair->getKeyMaterial())) {
+        $url = Url::fromRoute('entity.aws_cloud_key_pair.download', [
+          'cloud_context' => $keypair->getCloudContext(),
+          'key_pair' => $keypair->id(),
+        ])->toString();
+        $build['#attached']['drupalSettings']['download_url'] = $url;
+        $build['#attached']['library'][] = 'aws_cloud/aws_cloud_key_pair_download';
+      }
+    }
+  }
+
+  /**
+   * Implements hook_cron().
+   */
+  #[Hook('cron')]
+  public static function cron(): void {
+    $ec2_service = \Drupal::service('aws_cloud.ec2');
+    $cfn_service = \Drupal::service('aws_cloud.cloud_formation');
+    /** @var \Drupal\aws_cloud\Access\AwsCloudAccess $aws_access_service */
+    $aws_access_service = \Drupal::service('aws_cloud.access');
+    $entities = \Drupal::service('plugin.manager.cloud_config_plugin')->loadConfigEntities('aws_cloud');
+    foreach ($entities ?: [] as $entity) {
+      if ($entity->isRemote()) {
+        continue;
+      }
+      $ec2_service->setCloudContext($entity->getCloudContext());
+      $ec2_service->createResourceQueueItems($entity);
+      $cfn_service->setCloudContext($entity->getCloudContext());
+      $cfn_service->createResourceQueueItems($entity);
+      // Update IAM permissions.
+      $aws_access_service->createResourceQueueItems($entity);
+      // @todo re-enabled instance bundling
+      // Update any instance types if needed.
+      aws_cloud_update_instance_types($entity);
+    }
+    // Notify owners and admins if their instances have been running for too long.
+    aws_cloud_notify_instance_owners_and_admins('long_running');
+    // Notify owners and admins if their instances have been running
+    // in low utilization status for several days.
+    aws_cloud_notify_instance_owners_and_admins('low_utilization');
+    // Notify admins about unused EBS volumes.
+    aws_cloud_notify_unused_volumes_owners_and_admins();
+    // Notify admins about unused EBS snapshots.
+    aws_cloud_notify_unused_snapshots_owners_and_admins();
+    // Notify admins about unused Elastic IPs.
+    aws_cloud_notify_unused_elastic_ips_owners_and_admins();
+    // Clear the cache.
+    \Drupal::service('cloud')->clearAllCacheValues();
+  }
+
+  /**
+   * Implements hook_rebuild().
+   */
+  #[Hook('rebuild')]
+  public static function rebuild(): void {
+    // Rebuild the instance type cache.
+    $entities = \Drupal::service('plugin.manager.cloud_config_plugin')->loadConfigEntities('aws_cloud');
+    foreach ($entities ?: [] as $entity) {
+      // Update any instance types if needed.
+      aws_cloud_update_instance_types($entity);
+    }
+  }
+
+  /**
+   * Implements hook_cloud_config_presave().
+   */
+  #[Hook('cloud_config_presave')]
+  public static function cloudConfigPresave(CloudConfig $cloud_config): void {
+    if ($cloud_config->bundle() === 'aws_cloud') {
+      $cloud_context = $cloud_config->get('cloud_context')->value;
+      $access_key = $cloud_config->get('field_access_key')->value;
+      $secret_key = $cloud_config->get('field_secret_key')->value;
+      if (!empty($access_key) && !empty($secret_key) && empty($cloud_config->get('field_use_instance_profile')->value)) {
+        // Create credential_file.
+        aws_cloud_create_credential_file($cloud_context, $access_key, $secret_key);
+        $cloud_config->set('field_access_key', NULL);
+        $cloud_config->set('field_secret_key', NULL);
+      }
+      if (!empty($cloud_config->get('field_get_price_list')->value)) {
+        // Update instance types.
+        aws_cloud_update_instance_types($cloud_config);
+      }
+      // Create or update a pricing spreadsheet.
+      aws_cloud_create_or_update_pricing_spreadsheet($cloud_config);
+    }
+  }
+
+  /**
+   * Implements hook_default_cloud_config_icon().
+   */
+  #[Hook('default_cloud_config_icon')]
+  public static function defaultCloudConfigIcon(EntityInterface $entity): ?int {
+    // Provides the calling hook with the default AWS icon.
+    return \Drupal::service('cloud')->getDefaultCloudConfigIcon($entity, 'aws_cloud');
+  }
+
+  /**
+   * Implements hook_cloud_config_update().
+   */
+  #[Hook('cloud_config_update')]
+  public static function cloudConfigUpdate(CloudConfig $cloud_config): void {
+    // Only perform resource update if coming from the cloud service provider
+    // edit form.  This hook can be triggered during user_delete().  user_delete()
+    // runs in progressive batch mode and `aws_cloud_cloud_config_update()` will
+    // try to run a second non-progressive batch mode.  This confuses Drupal
+    // and the original batch processing will never complete, causing the
+    // site to hang.
+    if ($cloud_config->bundle() === 'aws_cloud' && !aws_cloud_cloud_configs_equal($cloud_config, DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => $cloud_config->getOriginal(), fn() => $cloud_config->original), [
+      'changed',
+      'field_spreadsheet_pricing_url',
+      'field_system_vpc',
+    ]) && \Drupal::routeMatch()->getRouteName() === 'entity.cloud_config.edit_form') {
+      // Update resources.
+      aws_cloud_update_ec2_resources($cloud_config);
+    }
+  }
+
+  /**
+   * Implements hook_cloud_config_predelete().
+   */
+  #[Hook('cloud_config_predelete')]
+  public static function cloudConfigPredelete(CloudConfig $cloud_config): void {
+    if ($cloud_config->bundle() === 'aws_cloud') {
+      \Drupal::service('cloud')->deleteQueue(\Drupal::service('cloud')->getResourceQueueName($cloud_config));
+    }
+  }
+
+  /**
+   * Implements hook_cloud_config_delete().
+   */
+  #[Hook('cloud_config_delete')]
+  public static function cloudConfigDelete(CloudConfig $cloud_config): void {
+    if ($cloud_config->bundle() === 'aws_cloud') {
+      $cloud_context = $cloud_config->getCloudContext();
+      /** @var \Drupal\aws_cloud\Service\Ec2\Ec2ServiceInterface $ec2_service */
+      $ec2_service = \Drupal::service('aws_cloud.ec2');
+      try {
+        $ec2_service->setCloudContext($cloud_context);
+      }
+      catch (\Exception $e) {
+      } finally {
+        \Drupal::service('cloud')->clearAllEntities('aws_cloud', $cloud_context);
+        // Clean up credential files.
+        $credential_file = aws_cloud_ini_file_path($cloud_config->get('cloud_context')->value);
+        \Drupal::service('file_system')->delete($credential_file);
+        // Delete the pricing spreadsheet.
+        aws_cloud_delete_pricing_spreadsheet($cloud_config);
+        \Drupal::service('cloud')->clearAllCacheValues();
+      }
+      \Drupal::service('aws_cloud.access')->deletePermissions($cloud_config->getCloudContext());
+    }
+  }
+
+  /**
+   * Implements hook_entity_operation().
+   */
+  #[Hook('entity_operation')]
+  public function entityOperation(EntityInterface $entity): array {
+    $operations = [];
+    $account = \Drupal::currentUser();
+    if ($entity->getEntityTypeId() === 'aws_cloud_instance') {
+      if ($account->hasPermission('edit any aws cloud instance') || $account->hasPermission('edit own aws cloud instance') && !empty($entity->getOwner()) && $account->id() === $entity->getOwner()->id()) {
+        if ($entity->getInstanceState() === 'running') {
+          $operations['stop'] = [
+            'title' => $this->t('Stop'),
+            'url' => $entity->toUrl('stop-form'),
+            'weight' => 20,
+          ];
+          $operations['reboot'] = [
+            'title' => $this->t('Reboot'),
+            'url' => $entity->toUrl('reboot-form'),
+            'weight' => 21,
+          ];
+        }
+        elseif ($entity->getInstanceState() === 'stopped') {
+          $operations['start'] = [
+            'title' => $this->t('Start'),
+            'url' => $entity->toUrl('start-form'),
+            'weight' => 20,
+          ];
+          // Add associate IP link.
+          if (aws_cloud_can_attach_ip($entity) === TRUE && count(aws_cloud_get_available_elastic_ips($entity->getCloudContext()))) {
+            $operations['associate_elastic_ip'] = [
+              'title' => $this->t('Associate Elastic IP'),
+              'url' => $entity->toUrl('associate-elastic-ip-form'),
+              'weight' => 22,
+            ];
+          }
+        }
+        elseif ($entity->getInstanceState() === 'shutting-down' || $entity->getInstanceState() === 'terminated') {
+          return $operations;
+        }
+        // Create Image.
+        $operations['create_image'] = [
+          'title' => $this->t('Create image'),
+          'url' => $entity->toUrl('create-image-form'),
+          'weight' => 21,
+        ];
+      }
+    }
+    elseif ($entity->getEntityTypeId() === 'aws_cloud_volume') {
+      if ($account->hasPermission('edit any aws cloud volume') || $account->hasPermission('edit own aws cloud volume') && !empty($entity->getOwner()) && $account->id() === $entity->getOwner()->id()) {
+        if ($entity->getState() === 'available') {
+          $operations['attach'] = [
+            'title' => $this->t('Attach'),
+            'url' => $entity->toUrl('attach-form'),
+            'weight' => 20,
+          ];
+        }
+        elseif ($entity->getState() === 'in-use') {
+          $operations['detach'] = [
+            'title' => $this->t('Detach'),
+            'url' => $entity->toUrl('detach-form'),
+            'weight' => 20,
+          ];
+        }
+      }
+      $url = Url::fromRoute('entity.aws_cloud_snapshot.add_form', [
+        'cloud_context' => $entity->getCloudContext(),
+      ]);
+      if ($url->access($account)) {
+        $operations['create_snapshot'] = [
+          'title' => $this->t('Create snapshot'),
+          'url' => Url::fromRoute('entity.aws_cloud_snapshot.add_form', [
+            'cloud_context' => $entity->getCloudContext(),
+            'volume_id' => $entity->getVolumeId(),
+          ]),
+          'weight' => 21,
+        ];
+      }
+    }
+    elseif ($entity->getEntityTypeId() === 'aws_cloud_elastic_ip') {
+      if ($account->hasPermission('edit any aws cloud elastic ip') || $account->hasPermission('edit own aws cloud elastic ip') && !empty($entity->getOwner()) && $account->id() === $entity->getOwner()->id()) {
+        if ($entity->getAssociationId() === NULL) {
+          $operations['associate'] = [
+            'title' => $this->t('Associate'),
+            'url' => $entity->toUrl('associate-form'),
+          ];
+        }
+        else {
+          $operations['disassociate'] = [
+            'title' => $this->t('Disassociate'),
+            'url' => $entity->toUrl('disassociate-form'),
+          ];
+        }
+      }
+    }
+    elseif ($entity->getEntityTypeId() === 'aws_cloud_snapshot') {
+      $url = Url::fromRoute('entity.aws_cloud_volume.add_form', [
+        'cloud_context' => $entity->getCloudContext(),
+      ]);
+      if ($url->access($account)) {
+        $operations['create_volume'] = [
+          'title' => $this->t('Create Volume'),
+          'url' => Url::fromRoute('entity.aws_cloud_volume.add_form', [
+            'cloud_context' => $entity->getCloudContext(),
+            'snapshot_id' => $entity->getSnapshotId(),
+          ]),
+          'weight' => 20,
+        ];
+      }
+    }
+    elseif ($entity->getEntityTypeId() === 'aws_cloud_security_group') {
+      if ($account->hasPermission('add aws cloud security group')) {
+        $operations['copy_security_group'] = [
+          'title' => $this->t('Copy'),
+          'url' => Url::fromRoute('entity.aws_cloud_security_group.copy_form', [
+            'cloud_context' => $entity->getCloudContext(),
+            'aws_cloud_security_group' => $entity->id(),
+          ]),
+          'weight' => 20,
+        ];
+      }
+    }
+    elseif ($entity->getEntityTypeId() === 'aws_cloud_vpc_peering_connection') {
+      if ($account->hasPermission('edit any aws cloud vpc peering connection') || $account->hasPermission('edit own aws cloud vpc peering connection') && $entity->getStatusCode() === 'pending-acceptance') {
+        $operations['accept_vpc_peering_connection'] = [
+          'title' => $this->t('Accept'),
+          'url' => Url::fromRoute('entity.aws_cloud_vpc_peering_connection.accept_form', [
+            'cloud_context' => $entity->getCloudContext(),
+            'aws_cloud_vpc_peering_connection' => $entity->id(),
+          ]),
+          'weight' => 20,
+        ];
+      }
+    }
+    elseif ($entity->getEntityTypeId() === 'aws_cloud_internet_gateway') {
+      if ($account->hasPermission('edit any aws cloud internet gateway') || $account->hasPermission('edit own aws cloud internet gateway') && !empty($entity->getOwner()) && $account->id() === $entity->getOwner()->id()) {
+        if ($entity->getVpcId() === NULL) {
+          $operations['attach'] = [
+            'title' => $this->t('Attach'),
+            'url' => $entity->toUrl('attach-form'),
+          ];
+        }
+        else {
+          $operations['detach'] = [
+            'title' => $this->t('Detach'),
+            'url' => $entity->toUrl('detach-form'),
+          ];
+        }
+      }
+    }
+    return $operations;
+  }
+
+  /**
+   * Implements hook_entity_operation_alter().
+   */
+  #[Hook('entity_operation_alter')]
+  public static function entityOperationAlter(array &$operations, EntityInterface $entity): void {
+    if ($entity->getEntityTypeId() === 'aws_cloud_volume') {
+      if ($entity->getState() === 'in-use') {
+        unset($operations['delete']);
+      }
+    }
+    if ($entity->getEntityTypeId() === 'aws_cloud_image') {
+      if ($entity->getStatus() === 'pending') {
+        unset($operations['delete']);
+      }
+    }
+    if ($entity->getEntityTypeId() === 'aws_cloud_instance') {
+      if ($entity->getInstanceState() === 'shutting-down' || $entity->getInstanceState() === 'terminated') {
+        unset($operations['edit']);
+        unset($operations['delete']);
+      }
+      if (isset($operations['edit'])) {
+        /** @var Drupal\Core\Url $url */
+        $url = $operations['edit']['url'];
+        $url->setOption('query', []);
+      }
+    }
+    if ($entity->getEntityTypeId() === 'aws_cloud_elastic_ip') {
+      $association_id = $entity->getAssociationId();
+      if (isset($association_id)) {
+        unset($operations['delete']);
+      }
+    }
+  }
+
+  /**
+   * Implements hook_query_TAG_Alter().
+   */
+  #[Hook('query_aws_cloud_instance_views_access_alter')]
+  public static function queryAwsCloudInstanceViewsAccessAlter(AlterableInterface $query): void {
+    if (!$account = $query->getMetaData('account')) {
+      $account = \Drupal::currentUser();
+    }
+    if ($account->hasPermission('view any aws cloud instance')) {
+      return;
+    }
+    else {
+      // Add a 'uid' condition.
+      $query->condition('aws_cloud_instance.uid', $account->id());
+    }
+  }
+
+  /**
+   * Implements hook_query_TAG_Alter().
+   */
+  #[Hook('query_aws_cloud_image_views_access_alter')]
+  public static function queryAwsCloudImageViewsAccessAlter(AlterableInterface $query): void {
+    if (!$account = $query->getMetaData('account')) {
+      $account = \Drupal::currentUser();
+    }
+    if ($account->hasPermission('view any aws cloud image')) {
+      return;
+    }
+    else {
+      // Add a 'uid' condition and let users view public images.
+      $or = new Condition('OR');
+      $or->condition('aws_cloud_image.uid', [
+        0,
+        $account->id(),
+      ], 'IN')->condition('aws_cloud_image.visibility', TRUE);
+      $query->condition($or);
+    }
+  }
+
+  /**
+   * Implements hook_query_TAG_Alter().
+   */
+  #[Hook('query_aws_cloud_security_group_views_access_alter')]
+  public static function queryAwsCloudSecurityGroupViewsAccessAlter(AlterableInterface $query): void {
+    if (!$account = $query->getMetaData('account')) {
+      $account = \Drupal::currentUser();
+    }
+    if ($account->hasPermission('view any aws cloud security group')) {
+      return;
+    }
+    else {
+      // Add a 'uid' condition.
+      $query->condition('aws_cloud_security_group.uid', $account->id());
+    }
+  }
+
+  /**
+   * Implements hook_query_TAG_Alter().
+   */
+  #[Hook('query_aws_cloud_elastic_ip_views_access_alter')]
+  public static function queryAwsCloudElasticIpViewsAccessAlter(AlterableInterface $query): void {
+    if (!$account = $query->getMetaData('account')) {
+      $account = \Drupal::currentUser();
+    }
+    if ($account->hasPermission('view any aws cloud elastic ip')) {
+      return;
+    }
+    else {
+      // Add a 'uid' condition.
+      $query->condition('aws_cloud_elastic_ip.uid', $account->id());
+    }
+  }
+
+  /**
+   * Implements hook_query_TAG_Alter().
+   */
+  #[Hook('query_aws_cloud_key_pair_views_access_alter')]
+  public static function queryAwsCloudKeyPairViewsAccessAlter(AlterableInterface $query): void {
+    if (!$account = $query->getMetaData('account')) {
+      $account = \Drupal::currentUser();
+    }
+    if ($account->hasPermission('view any aws cloud key pair')) {
+      return;
+    }
+    else {
+      // Add a 'uid' condition.
+      $query->condition('aws_cloud_key_pair.uid', $account->id());
+    }
+  }
+
+  /**
+   * Implements hook_query_TAG_Alter().
+   */
+  #[Hook('query_aws_cloud_network_interface_views_access_alter')]
+  public static function queryAwsCloudNetworkInterfaceViewsAccessAlter(AlterableInterface $query): void {
+    if (!$account = $query->getMetaData('account')) {
+      $account = \Drupal::currentUser();
+    }
+    if ($account->hasPermission('view any aws cloud network interface')) {
+      return;
+    }
+    else {
+      // Add a 'uid' condition.
+      $query->condition('aws_cloud_network_interface.uid', $account->id());
+    }
+  }
+
+  /**
+   * Implements hook_query_TAG_Alter().
+   */
+  #[Hook('query_aws_cloud_snapshot_views_access_alter')]
+  public static function queryAwsCloudSnapshotViewsAccessAlter(AlterableInterface $query): void {
+    if (!$account = $query->getMetaData('account')) {
+      $account = \Drupal::currentUser();
+    }
+    if ($account->hasPermission('view any aws cloud snapshot')) {
+      return;
+    }
+    else {
+      // Add a 'uid' condition.
+      $query->condition('aws_cloud_snapshot.uid', $account->id());
+    }
+  }
+
+  /**
+   * Implements hook_query_TAG_Alter().
+   */
+  #[Hook('query_aws_cloud_volume_views_access_alter')]
+  public static function queryAwsCloudVolumeViewsAccessAlter(AlterableInterface $query): void {
+    if (!$account = $query->getMetaData('account')) {
+      $account = \Drupal::currentUser();
+    }
+    if ($account->hasPermission('view any aws cloud volume')) {
+      return;
+    }
+    else {
+      // Add a 'uid' condition.
+      $query->condition('aws_cloud_volume.uid', $account->id());
+    }
+  }
+
+  /**
+   * Implements hook_query_TAG_Alter().
+   */
+  #[Hook('query_aws_cloud_vpc_views_access_alter')]
+  public static function queryAwsCloudVpcViewsAccessAlter(AlterableInterface $query): void {
+    if (!$account = $query->getMetaData('account')) {
+      $account = \Drupal::currentUser();
+    }
+    if ($account->hasPermission('view any aws cloud vpc')) {
+      return;
+    }
+    else {
+      // Add a 'uid' condition.
+      $query->condition('aws_cloud_vpc.uid', $account->id());
+    }
+  }
+
+  /**
+   * Implements hook_query_TAG_Alter().
+   */
+  #[Hook('query_aws_cloud_subnet_views_access_alter')]
+  public static function queryAwsCloudSubnetViewsAccessAlter(AlterableInterface $query): void {
+    if (!$account = $query->getMetaData('account')) {
+      $account = \Drupal::currentUser();
+    }
+    if ($account->hasPermission('view any aws cloud subnet')) {
+      return;
+    }
+    else {
+      // Add a 'uid' condition.
+      $query->condition('aws_cloud_subnet.uid', $account->id());
+    }
+  }
+
+  /**
+   * Implements hook_query_TAG_Alter().
+   */
+  #[Hook('query_aws_cloud_vpc_peering_connection_views_access_alter')]
+  public static function queryAwsCloudVpcPeeringConnectionViewsAccessAlter(AlterableInterface $query): void {
+    if (!$account = $query->getMetaData('account')) {
+      $account = \Drupal::currentUser();
+    }
+    if ($account->hasPermission('view any aws cloud vpc peering connection')) {
+      return;
+    }
+    else {
+      // Add a 'uid' condition.
+      $query->condition('aws_cloud_vpc_peering_connection.uid', $account->id());
+    }
+  }
+
+  /**
+   * Implements hook_query_TAG_Alter().
+   */
+  #[Hook('query_aws_cloud_internet_gateway_views_access_alter')]
+  public static function queryAwsCloudInternetGatewayViewsAccessAlter(AlterableInterface $query): void {
+    if (!$account = $query->getMetaData('account')) {
+      $account = \Drupal::currentUser();
+    }
+    if ($account->hasPermission('view any aws cloud internet gateway')) {
+      return;
+    }
+    else {
+      // Add a 'uid' condition.
+      $query->condition('aws_cloud_internet_gateway.uid', $account->id());
+    }
+  }
+
+  /**
+   * Implements hook_query_TAG_Alter().
+   */
+  #[Hook('query_aws_cloud_carrier_gateway_views_access_alter')]
+  public static function queryAwsCloudCarrierGatewayViewsAccessAlter(AlterableInterface $query): void {
+    if (!$account = $query->getMetaData('account')) {
+      $account = \Drupal::currentUser();
+    }
+    if ($account->hasPermission('view any aws cloud carrier gateway')) {
+      return;
+    }
+    else {
+      // Add a 'uid' condition.
+      $query->condition('aws_cloud_carrier_gateway.uid', $account->id());
+    }
+  }
+
+  /**
+   * Implements hook_query_TAG_Alter().
+   */
+  #[Hook('query_aws_cloud_transit_gateway_views_access_alter')]
+  public static function queryAwsCloudTransitGatewayViewsAccessAlter(AlterableInterface $query): void {
+    if (!$account = $query->getMetaData('account')) {
+      $account = \Drupal::currentUser();
+    }
+    if ($account->hasPermission('view any aws cloud transit gateway')) {
+      return;
+    }
+    else {
+      // Add a 'uid' condition.
+      $query->condition('aws_cloud_transit_gateway.uid', $account->id());
+    }
+  }
+
+  /**
+   * Implements hook_query_TAG_Alter().
+   */
+  #[Hook('query_aws_cloud_stack_views_access_alter')]
+  public static function queryAwsCloudStackViewsAccessAlter(AlterableInterface $query): void {
+    if (!$account = $query->getMetaData('account')) {
+      $account = \Drupal::currentUser();
+    }
+    if ($account->hasPermission('view any aws cloud stack')) {
+      return;
+    }
+    else {
+      // Add a 'uid' condition.
+      $query->condition('aws_cloud_stack.uid', $account->id());
+    }
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_launch_template_aws_cloud_launch_form_alter')]
+  public function formCloudLaunchTemplateAwsCloudLaunchFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    /** @var \Drupal\cloud\Entity\CloudLaunchTemplate $cloud_launch_template */
+    $cloud_launch_template = \Drupal::routeMatch()->getParameter('cloud_launch_template');
+    $cloud_context = \Drupal::routeMatch()->getParameter('cloud_context');
+    $config = \Drupal::config('aws_cloud.settings');
+    if ($config->get('aws_cloud_instance_type_cost_list')) {
+      $form['cost'] = [
+        '#type' => 'details',
+        '#title' => $this->t('Cost'),
+        '#open' => TRUE,
+      ];
+      $price_table_renderer = \Drupal::service('aws_cloud.instance_type_price_table_renderer');
+      $form['cost']['price_table'] = $price_table_renderer->render($cloud_context, $cloud_launch_template->get('field_instance_type')->value);
+    }
+    $form['automation'] = [
+      '#type' => 'details',
+      '#title' => $this->t('Automation'),
+      '#open' => TRUE,
+    ];
+    $form['automation']['description'] = $form['description'];
+    unset($form['description']);
+    $form['automation']['termination_protection'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Termination protection'),
+      '#description' => $this->t('Enable the termination protection. If enabled, this instance cannot be terminated using the console, API, or CLI until termination protection is disabled.'),
+      '#default_value' => $form_state->getFormObject()->getEntity()->get('field_termination_protection')->value === '1',
+    ];
+    $config = \Drupal::config('aws_cloud.settings');
+    $form['automation']['terminate'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Automatically terminate instance'),
+      '#description' => $this->t('Terminate instance automatically.  Specify termination date in the date picker below.'),
+      '#default_value' => $config->get('aws_cloud_instance_terminate'),
+    ];
+    // @todo make 30 days configurable
+    $form['automation']['termination_date'] = [
+      '#type' => 'datetime',
+      '#title' => $this->t('Termination Date'),
+      '#description' => $this->t('The default termination date is 30 days into the future.'),
+      '#default_value' => DrupalDateTime::createFromTimestamp(time() + 2592000),
+    ];
+    /** @var \Drupal\cloud\Entity\CloudLaunchTemplate $cloud_launch_template */
+    $cloud_launch_template = \Drupal::routeMatch()->getParameter('cloud_launch_template');
+    $form['automation']['schedule'] = [
+      '#title' => $this->t('Schedule'),
+      '#type' => 'select',
+      '#default_value' => $cloud_launch_template->get('field_schedule')->value,
+      '#options' => aws_cloud_get_schedule(),
+      '#description' => $this->t('Configure start and stop schedule. This helps reduce server hosting costs.'),
+    ];
+    if ($cloud_launch_template->get('field_instance_shutdown_behavior')->value === 'terminate') {
+      // Add a warning message setting a schedule will terminate the instance,
+      // since the shutdown behavior is equal to 'terminate'.
+      $form['automation']['terminate_message'] = [
+        '#markup' => $this->t('Setting a schedule will potentially terminate the instance since the <strong>%text</strong> is set to Terminate', [
+          '%text' => 'Instance shutdown behavior',
+        ]),
+      ];
+    }
+    $cloud_config_plugin = \Drupal::service('plugin.manager.cloud_config_plugin');
+    $cloud_config_plugin->setCloudContext($cloud_context);
+    $cloud_config = $cloud_config_plugin->loadConfigEntity();
+    $form['automation']['automatically_assign_vpc'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Automatically assign a VPC'),
+      '#description' => $this->t('Assign a VPC automatically.'),
+      '#default_value' => $cloud_config->get('field_automatically_assign_vpc')->value,
+    ];
+    $form['bastion'] = [
+      '#type' => 'details',
+      '#title' => $this->t('Bastion'),
+      '#open' => TRUE,
+    ];
+    $form['bastion']['as_bastion'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('As a bastion instance'),
+      '#description' => $this->t('Launch as a bastion instance.'),
+      '#default_value' => FALSE,
+      '#states' => [
+        'visible' => [
+          ':input[name="bastion_instance"]' => [
+            'value' => '',
+          ],
+        ],
+      ],
+    ];
+    $bastion_instance_options = aws_cloud_get_bastion_instance_options($cloud_context);
+    if (!empty($bastion_instance_options)) {
+      $form['bastion']['bastion_instance'] = [
+        '#type' => 'select',
+        '#title' => $this->t('Use a bastion instance'),
+        '#description' => $this->t("Make a connection from the bastion instance's VPC automatically."),
+        '#options' => $bastion_instance_options,
+        '#empty_value' => '',
+        '#empty_option' => $this->t('None'),
+        '#states' => [
+          'invisible' => [
+            ':input[name="as_bastion"]' => [
+              'checked' => TRUE,
+            ],
+          ],
+        ],
+      ];
+    }
+    $view_builder = \Drupal::entityTypeManager()->getViewBuilder('cloud_launch_template');
+    $build = $view_builder->view($cloud_launch_template, 'view');
+    unset($build['#weight']);
+    $build['#pre_render'][] = '\Drupal\aws_cloud\Entity\Ec2\AwsCloudViewBuilder::reorderLaunchTemplate';
+    $form['detail'] = $build;
+    $form['#validate'][] = 'aws_cloud_form_cloud_launch_template_aws_cloud_launch_form_validate';
+    $form['actions']['submit']['#submit'][1] = 'aws_cloud_form_cloud_launch_template_aws_cloud_launch_form_submit';
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_launch_template_aws_cloud_approve_form_alter')]
+  public static function formCloudLaunchTemplateAwsCloudApproveFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    $form['actions']['submit']['#submit'][1] = 'aws_cloud_form_cloud_launch_template_aws_cloud_approve_form_submit';
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_launch_template_aws_cloud_review_form_alter')]
+  public static function formCloudLaunchTemplateAwsCloudReviewFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    $form['actions']['submit']['#submit'][1] = 'aws_cloud_form_cloud_launch_template_aws_cloud_review_form_submit';
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_launch_template_aws_cloud_edit_form_alter')]
+  public static function formCloudLaunchTemplateAwsCloudEditFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    aws_cloud_form_cloud_launch_template_aws_cloud_form_common_alter($form, $form_state, $form_id);
+    // Hide new revision checkbox.
+    $form['new_revision']['#access'] = FALSE;
+    // Disable name field.
+    $form['instance']['name']['#disabled'] = TRUE;
+    // Overwrite function ::save.
+    $form['actions']['submit']['#submit'][1] = 'aws_cloud_form_cloud_launch_template_aws_cloud_edit_form_submit';
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_launch_template_aws_cloud_add_form_alter')]
+  public static function formCloudLaunchTemplateAwsCloudAddFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    aws_cloud_form_cloud_launch_template_aws_cloud_form_common_alter($form, $form_state, $form_id);
+    // Overwrite function ::save.
+    $form['actions']['submit']['#submit'][1] = 'aws_cloud_form_cloud_launch_template_aws_cloud_add_form_submit';
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_launch_template_revision_delete_confirm_alter')]
+  public static function formCloudLaunchTemplateRevisionDeleteConfirmAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    $form['#submit'] = [
+      'aws_cloud_form_cloud_launch_template_revision_delete_confirm_submit',
+    ];
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_launch_template_revision_revert_confirm_alter')]
+  public static function formCloudLaunchTemplateRevisionRevertConfirmAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    $form['#submit'] = [
+      'aws_cloud_form_cloud_launch_template_revision_revert_confirm_submit',
+    ];
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_launch_template_aws_cloud_copy_form_alter')]
+  public function formCloudLaunchTemplateAwsCloudCopyFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    aws_cloud_form_cloud_launch_template_aws_cloud_form_common_alter($form, $form_state, $form_id);
+    // Change name for copy.
+    $name = $form['instance']['name']['widget'][0]['value']['#default_value'];
+    $form['instance']['name']['widget'][0]['value']['#default_value'] = $this->t('copy_of_@name', [
+      '@name' => $name,
+    ]);
+    // Hide new revision checkbox.
+    $form['new_revision']['#access'] = FALSE;
+    // Clear the revision log message.
+    $form['others']['revision_log_message']['widget'][0]['value']['#default_value'] = NULL;
+    // Change value of the submit button.
+    $form['actions']['submit']['#value'] = $this->t('Copy');
+    // Delete the delete button.
+    $form['actions']['delete']['#access'] = FALSE;
+    // Overwrite function ::save.
+    $form['actions']['submit']['#submit'][1] = 'aws_cloud_form_cloud_launch_template_aws_cloud_copy_form_submit';
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_launch_template_aws_cloud_delete_form_alter')]
+  public static function formCloudLaunchTemplateAwsCloudDeleteFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    $form['actions']['submit']['#submit'] = [
+      'aws_cloud_form_cloud_launch_template_aws_cloud_delete_form_submit',
+    ];
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   *
+   * Alter form cloud_config_aws_cloud_delete_form.
+   */
+  #[Hook('form_cloud_config_aws_cloud_delete_form_alter')]
+  public static function formCloudConfigAwsCloudDeleteFormAlter(&$form, FormStateInterface $form_state, $form_id): void {
+    $form['actions']['submit']['#submit'] = [
+      'aws_cloud_form_cloud_config_aws_cloud_delete_form_submit',
+    ];
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   *
+   * @todo Refactor this function.  This should be moved to cloud.module because
+   * it is responsible for _ALL_ cloud service providers.  The underlying delete
+   * functionality should be reworked also.
+   *
+   * Alter form cloud_config_delete_multiple_confirm_form.
+   */
+  #[Hook('form_cloud_config_delete_multiple_confirm_form_alter')]
+  public static function formCloudConfigDeleteMultipleConfirmFormAlter(&$form, FormStateInterface $form_state, $form_id): void {
+    $form['actions']['submit']['#submit'] = [
+      'aws_cloud_form_cloud_config_delete_multiple_confirm_form_submit',
+    ];
+  }
+
+  /**
+   * Implements hook_preprocess_field_multiple_value_form().
+   */
+  #[Hook('preprocess_field_multiple_value_form')]
+  public function preprocessFieldMultipleValueForm(array &$variables): void {
+    // Disable reordering of the ip_permission field.
+    if ($variables['element']['#field_name'] === 'ip_permission' || $variables['element']['#field_name'] === 'outbound_permission') {
+      aws_cloud_remove_table_reordering($variables['table']);
+      // Switch the text of "Add another item" to "Add Rule".
+      $variables['element']['add_more']['#value'] = $this->t('Add Rule');
+      $variables['button']['#value'] = $this->t('Add Rule');
+    }
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_config_aws_cloud_edit_form_alter')]
+  public function formCloudConfigAwsCloudEditFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    $form['#validate'][] = 'aws_cloud_form_cloud_config_aws_cloud_credentials_validate';
+    $form['#validate'][] = 'aws_cloud_form_cloud_config_aws_cloud_check_iam_permissions_validate';
+    $max_region_code_length = CloudConfigInterface::MAX_NAME_LENGTH;
+    $form['name']['widget'][0]['value']['#description'] = $this->t('The name of the cloud service provider. The maximum number of characters allowed in the name is %max_region_code_length.', [
+      '%max_region_code_length' => $max_region_code_length,
+    ]);
+    $form['name']['widget'][0]['value']['#maxlength'] = $max_region_code_length;
+    aws_cloud_form_cloud_config_aws_cloud_form_common_alter($form, $form_state, $form_id);
+    // Location fieldset is unnecessary for edit form.
+    unset($form['profile']['location']);
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_config_aws_cloud_add_form_alter')]
+  public function formCloudConfigAwsCloudAddFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    // Hide fields which will be set automatically.
+    $form['cloud_context']['#access'] = FALSE;
+    $form['field_region']['#access'] = FALSE;
+    $form['field_api_endpoint_uri']['#access'] = FALSE;
+    $form['field_image_upload_url']['#access'] = FALSE;
+    $form['field_system_vpc']['#access'] = FALSE;
+    $form['field_location_country']['#access'] = FALSE;
+    $form['field_location_city']['#access'] = FALSE;
+    $form['field_location_longitude']['#access'] = FALSE;
+    $form['field_location_latitude']['#access'] = FALSE;
+    // Hide the field_check_iam_permissions from the add form.
+    $form['field_check_iam_permissions']['#access'] = FALSE;
+    $form['actions']['submit']['#submit'] = [
+      'aws_cloud_form_cloud_config_aws_cloud_add_form_submit',
+    ];
+    $form['#validate'][] = 'aws_cloud_form_cloud_config_aws_cloud_add_form_validate';
+    $form['#validate'][] = 'aws_cloud_form_cloud_config_aws_cloud_credentials_validate';
+    $form['#validate'][] = 'aws_cloud_form_cloud_config_aws_cloud_check_iam_permissions_validate';
+    // Get all regions.
+    $regions = \Drupal::service('aws_cloud.ec2')->getRegions();
+    $region_names = array_values(preg_replace('/^.*(\(.*\))/', '$1', $regions));
+    // Get the <max length of region code> + 1 (one white space)
+    $max_region_name = 0;
+    foreach ($region_names ?: [] as $region_name) {
+      $max_region_name = max($max_region_name, strlen($region_name ?? ''));
+    }
+    $max_region_code_length = CloudConfigInterface::MAX_NAME_LENGTH - ($max_region_name + 1);
+    $max_name_length = CloudConfigInterface::MAX_NAME_LENGTH;
+    $form['name']['widget'][0]['value']['#description'] = $this->t('Enter the name of the cloud service provider. The region name will be automatically added. The maximum number of characters of the name including the region name is %max_name_length.', [
+      '%max_name_length' => $max_name_length,
+    ]);
+    $form['name']['widget'][0]['value']['#maxlength'] = $max_region_code_length;
+    aws_cloud_form_cloud_config_aws_cloud_form_common_alter($form, $form_state, $form_id);
+    // VPC fieldset is unnecessary for add form.
+    unset($form['vpc']);
+    // Location fieldset is unnecessary for add form.
+    unset($form['profile']['location']);
+    // Add checkboxes to enable regions.
+    $ec2_service = \Drupal::service('aws_cloud.ec2');
+    $regions = $ec2_service->getRegions();
+    $form['profile']['common']['regions'] = [
+      '#title' => $this->t('Regions'),
+      '#description' => $this->t('Select regions to create cloud service providers.'),
+      '#type' => 'select',
+      '#options' => $regions,
+      '#required' => TRUE,
+      '#multiple' => TRUE,
+      '#size' => count($regions),
+      '#weight' => $form['profile']['common']['field_account_id']['#weight'] + 1,
+    ];
+    // Add radios asking user for iam validation preference.
+    $form['profile']['common']['iam_validation_options'] = [
+      '#title' => $this->t('Validate IAM permissions'),
+      '#description' => $this->t('Validate that the AWS account ID has all the necessary IAM permissions.'),
+      '#type' => 'radios',
+      '#options' => [
+        Ec2ServiceInterface::IAM_VALIDATION_NO_CHECK => $this->t('Do not validate IAM permissions'),
+        Ec2ServiceInterface::IAM_VALIDATION_ONE_REGION => $this->t('Validate one region'),
+        Ec2ServiceInterface::IAM_VALIDATION_ALL_REGIONS => $this->t('Validate all regions'),
+      ],
+      '#default_value' => Ec2ServiceInterface::IAM_VALIDATION_ONE_REGION,
+      '#weight' => $form['profile']['common']['regions']['#weight'] + 1,
+      '#required' => TRUE,
+    ];
+  }
+
+  /**
+   * Implements hook_entity_view_alter().
+   */
+  #[Hook('entity_view_alter')]
+  public static function entityViewAlter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display): void {
+    if ($entity->getEntityTypeId() === 'cloud_launch_template' && $entity->bundle() === 'aws_cloud') {
+      \Drupal::service('cloud')->reorderForm($build, aws_cloud_launch_template_field_orders(FALSE));
+      $build['#attached']['library'][] = 'aws_cloud/aws_cloud_view_builder';
+    }
+    if ($entity->getEntityTypeId() === 'aws_cloud_image' && !empty($build['visibility'][0]['#markup'])) {
+      if ($build['visibility'][0]['#markup']->__toString() === 'Off') {
+        $build['visibility'][0]['#markup'] = 'Private';
+      }
+      else {
+        $build['visibility'][0]['#markup'] = 'Public';
+      }
+    }
+  }
+
+  /**
+   * Implements hook_page_attachments().
+   */
+  #[Hook('page_attachments')]
+  public static function pageAttachments(array &$attachments): void {
+    $route = \Drupal::routeMatch();
+    if (in_array($route->getRouteName(), [
+      'view.aws_cloud_instance.list',
+      'view.aws_cloud_image.list',
+      'view.aws_cloud_snapshot.list',
+      'view.aws_cloud_volume.list',
+    ])) {
+      $attachments['#attached']['library'][] = 'aws_cloud/aws_cloud_auto_refresh';
+      $config = \Drupal::config('aws_cloud.settings');
+      $attachments['#attached']['drupalSettings']['aws_cloud_view_refresh_interval'] = $config->get('aws_cloud_view_refresh_interval');
+    }
+  }
+
+  /**
+   * Implements hook_views_pre_view().
+   */
+  #[Hook('views_pre_view')]
+  public static function viewsPreView($view, $display_id, array &$args): void {
+    if ($view->id() === 'cloud_config') {
+      $config = \Drupal::config('aws_cloud.settings');
+      if (empty($config->get('aws_cloud_instance_type_prices'))) {
+        $view->removeHandler($display_id, 'field', 'pricing_internal_cloud_config');
+      }
+      if (empty($config->get('aws_cloud_instance_type_prices_spreadsheet'))) {
+        $view->removeHandler($display_id, 'field', 'pricing_external_cloud_config');
+      }
+      return;
+    }
+    if ($view->id() === 'aws_cloud_instance') {
+      $config = \Drupal::config('aws_cloud.settings');
+      if (empty($config->get('aws_cloud_instance_list_cost_column'))) {
+        $view->removeHandler($display_id, 'field', 'cost');
+      }
+    }
+  }
+
+  /**
+   * Implements hook_menu_local_tasks_alter().
+   */
+  #[Hook('menu_local_tasks_alter')]
+  public static function menuLocalTasksAlter(array &$data, $route_name): void {
+    $config = \Drupal::config('aws_cloud.settings');
+    if (empty($config->get('aws_cloud_instance_type_prices'))) {
+      unset($data['tabs'][0]['aws_cloud.local_tasks.instance_type_price']);
+    }
+  }
+
+  /**
+   * Implements hook_menu_local_actions_alter().
+   */
+  #[Hook('menu_local_actions_alter')]
+  public static function menuLocalActionsAlter(array &$local_actions): void {
+    $config = \Drupal::config('aws_cloud.settings');
+    if (empty($config->get('aws_cloud_instance_type_prices'))) {
+      unset($local_actions['aws_cloud.instance_type_prices']);
+    }
+  }
+
+  /**
+   * Implements hook_form_alter().
+   */
+  #[Hook('form_alter')]
+  public function formAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    if (strpos($form_id, 'views_form_aws_cloud_') === 0) {
+      $form['#submit'][] = 'cloud_views_bulk_form_submit';
+    }
+    if ($form['#id'] === 'views-exposed-form-aws-cloud-image-list') {
+      $form['visibility']['#options'][1] = $this->t('Public');
+      $form['visibility']['#options'][0] = $this->t('Private');
+    }
+  }
+
+  /**
+   * Implements hook_ENTITY_TYPE_view_alter().
+   */
+  #[Hook('cloud_config_view_alter')]
+  public static function cloudConfigViewAlter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display): void {
+    if ($entity->bundle() === 'aws_cloud') {
+      $map_json_url = \Drupal::config('cloud.settings')->get('cloud_custom_location_map_json_url');
+      $url = Url::fromRoute('entity.cloud_config.location', [
+        'cloud_config' => $entity->id(),
+      ])->toString();
+      $build['cloud_config_location_map'] = [
+        '#markup' => '<div id="cloud_config_location"></div>',
+        '#attached' => [
+          'library' => [
+            'cloud/cloud_config_location',
+          ],
+          'drupalSettings' => [
+            'cloud' => [
+              'cloud_location_map_json_url' => $map_json_url,
+              'cloud_config_location_json_url' => $url,
+            ],
+          ],
+        ],
+      ];
+      $build['field_location_country']['#access'] = FALSE;
+      $build['field_location_city']['#access'] = FALSE;
+      $build['field_location_longitude']['#access'] = FALSE;
+      $build['field_location_latitude']['#access'] = FALSE;
+      aws_cloud_cloud_config_fieldsets($build);
+      $weight = $build['profile']['common']['#weight'];
+      $build['profile']['location']['#weight'] = --$weight;
+    }
+  }
+
+  /**
+   * Implements hook_query_TAG_Alter().
+   */
+  #[Hook('query_all_aws_cloud_resource_views_access_alter')]
+  public static function queryAllAwsCloudResourceViewsAccessAlter(AlterableInterface $query): void {
+    // Get table name.
+    $query_tables = $query->getTables();
+    foreach ($query_tables ?: [] as $info) {
+      if ($info['table'] instanceof SelectInterface) {
+        // @FIXME The single `continue` statement doesn't make sense below.
+        continue;
+      }
+    }
+    if (empty($info['table'])) {
+      return;
+    }
+    $table_name = $info['table'];
+    if (!$account = $query->getMetaData('account')) {
+      $account = \Drupal::currentUser();
+    }
+    $entities = \Drupal::service('plugin.manager.cloud_config_plugin')->loadConfigEntities('aws_cloud');
+    $cloud_contexts = [];
+    foreach ($entities ?: [] as $entity) {
+      if ($account->hasPermission('view all cloud service providers') || $account->hasPermission("view {$entity->getCloudContext()}")) {
+        $cloud_contexts[] = $entity->getCloudContext();
+      }
+    }
+    if (count($cloud_contexts)) {
+      $query->condition("{$table_name}.cloud_context", $cloud_contexts, 'IN');
+    }
+    else {
+      // No permissions, do not let them view any cloud context.
+      // Return an empty page.  This is just a catch-all.  In
+      // normal cases, users will have access to certain cloud context.
+      $query->condition("{$table_name}.cloud_context", '');
+    }
+  }
+
+}
diff --git a/modules/cloud_service_providers/aws_cloud/src/Hook/AwsCloudTokensHooks.php b/modules/cloud_service_providers/aws_cloud/src/Hook/AwsCloudTokensHooks.php
new file mode 100644
index 0000000..1db5a25
--- /dev/null
+++ b/modules/cloud_service_providers/aws_cloud/src/Hook/AwsCloudTokensHooks.php
@@ -0,0 +1,292 @@
+<?php
+
+namespace Drupal\aws_cloud\Hook;
+
+use Drupal\Core\Render\BubbleableMetadata;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for aws_cloud.
+ */
+class AwsCloudTokensHooks {
+  use StringTranslationTrait;
+
+  /**
+   * Implements hook_token_info().
+   */
+  #[Hook('token_info')]
+  public function tokenInfo(): array {
+    $types['aws_cloud_instance'] = [
+      'name' => $this->t('EC2 instances'),
+      'description' => $this->t('Tokens related to individual EC2 instances.'),
+      'needs-data' => 'aws_cloud_instance',
+    ];
+    $instance['name'] = [
+      'name' => $this->t('EC2 instance name'),
+      'description' => $this->t('The name of the EC2 instance entity.'),
+    ];
+    $instance['id'] = [
+      'name' => $this->t('EC2 instance ID'),
+      'description' => $this->t('EC2 instance ID.'),
+    ];
+    $instance['launch_time'] = [
+      'name' => $this->t('Launch time'),
+      'description' => $this->t('The time the EC2 instance launched.'),
+    ];
+    $instance['instance_state'] = [
+      'name' => $this->t('Instance state'),
+      'description' => $this->t('The current state of the instance.'),
+    ];
+    $instance['availability_zone'] = [
+      'name' => $this->t('Availability Zone'),
+      'description' => $this->t('Zone the instance is in.'),
+    ];
+    $instance['private_ip'] = [
+      'name' => $this->t('Private IP address'),
+      'description' => $this->t('The EC2 instance private IP.'),
+    ];
+    $instance['public_ip'] = [
+      'name' => $this->t('Public IP address'),
+      'description' => $this->t('The EC2 instance public IP.'),
+    ];
+    $instance['elastic_ip'] = [
+      'name' => $this->t('Elastic IP address'),
+      'description' => $this->t('The instance Elastic IP address.'),
+    ];
+    $instance['instance_link'] = [
+      'name' => $this->t('EC2 instance Link'),
+      'description' => $this->t('Absolute link to the EC2 instance.'),
+    ];
+    $instance['instance_edit_link'] = [
+      'name' => $this->t('Edit EC2 instance link'),
+      'description' => $this->t('Absolute link to edit the EC2 instance.'),
+    ];
+    $types['aws_cloud_volume'] = [
+      'name' => $this->t('EBS volume'),
+      'description' => $this->t('Tokens related to individual EBS volumes.'),
+      'needs-data' => 'aws_cloud_volume',
+    ];
+    $volume['name'] = [
+      'name' => $this->t('EBS volume name'),
+      'description' => $this->t('The name of the EBS volume entity.'),
+    ];
+    $volume['volume_link'] = [
+      'name' => $this->t('EBS volume Link'),
+      'description' => $this->t('Absolute link to EBS volume.'),
+    ];
+    $volume['volume_edit_link'] = [
+      'name' => $this->t('EBS volume edit link'),
+      'description' => $this->t('Absolute link to edit the EBS volume.'),
+    ];
+    $volume['created'] = [
+      'name' => $this->t('Create date'),
+      'description' => $this->t('The EBS volume create date.'),
+    ];
+    $types['aws_cloud_snapshot'] = [
+      'name' => $this->t('EBS snapshot'),
+      'description' => $this->t('Tokens related to individual EBS snapshots.'),
+      'needs-data' => 'aws_cloud_snapshot',
+    ];
+    $snapshot['name'] = [
+      'name' => $this->t('EBS snapshot name'),
+      'description' => $this->t('The name of the EBS snapshot entity.'),
+    ];
+    $snapshot['snapshot_link'] = [
+      'name' => $this->t('EBS snapshot Link'),
+      'description' => $this->t('Absolute link to EBS snapshot.'),
+    ];
+    $snapshot['snapshot_edit_link'] = [
+      'name' => $this->t('EBS snapshot Edit Link'),
+      'description' => $this->t('Absolute link to edit the EBS snapshot.'),
+    ];
+    $snapshot['created'] = [
+      'name' => $this->t('Create date'),
+      'description' => $this->t('The EBS snapshot create date.'),
+    ];
+    $types['aws_cloud_elastic_ip'] = [
+      'name' => $this->t('Elastic IP'),
+      'description' => $this->t('Tokens related to individual Elastic IPs.'),
+      'needs-data' => 'aws_cloud_elastic_ip',
+    ];
+    $elastic_ip['name'] = [
+      'name' => $this->t('Elastic IP name'),
+      'description' => $this->t('The name of the Elastic IP entity.'),
+    ];
+    $elastic_ip['elastic_ip_link'] = [
+      'name' => $this->t('Elastic IP Link'),
+      'description' => $this->t('Absolute link to Elastic IP.'),
+    ];
+    $elastic_ip['elastic_ip_edit_link'] = [
+      'name' => $this->t('Elastic IP Edit Link'),
+      'description' => $this->t('Absolute link to edit the Elastic IP.'),
+    ];
+    $elastic_ip['created'] = [
+      'name' => $this->t('Create date'),
+      'description' => $this->t('The Elastic IP create date.'),
+    ];
+    $types['aws_cloud_launch_template'] = [
+      'name' => $this->t('Launch Template'),
+      'description' => $this->t('Tokens related to individual launch template.'),
+      'needs-data' => 'aws_cloud_launch_template',
+    ];
+    $launch_template['name'] = [
+      'name' => $this->t('List of launch template name'),
+      'description' => $this->t('Enter the name of the launch template entity.'),
+    ];
+    $launch_template['launch_template_link'] = [
+      'name' => $this->t('Launch template link'),
+      'description' => $this->t('An absolute link to launch template.'),
+    ];
+    $launch_template['launch_template_edit_link'] = [
+      'name' => $this->t('Launch template edit link'),
+      'description' => $this->t('An absolute link to edit the launch template.'),
+    ];
+    $launch_template['changed'] = [
+      'name' => $this->t('Change date'),
+      'description' => $this->t('The launch template change date.'),
+    ];
+    $types['aws_cloud_instance_email'] = [
+      'name' => $this->t('EC2 instance email'),
+      'description' => $this->t('Tokens related to individual EC2 instance email.'),
+      'needs-data' => 'aws_cloud_instance_email',
+    ];
+    $instance_email['instances'] = [
+      'name' => $this->t('List of EC2 instances'),
+      'description' => $this->t('List of EC2 instances to display to user.'),
+    ];
+    $types['aws_cloud_volume_email'] = [
+      'name' => $this->t('EBS volume email'),
+      'description' => $this->t('Tokens related to individual EBS volume email.'),
+      'needs-data' => 'aws_cloud_volume_email',
+    ];
+    $volume_email['volumes'] = [
+      'name' => $this->t('List of EBS volumes'),
+      'description' => $this->t('List of EBS volumes to display to user.'),
+    ];
+    $types['aws_cloud_snapshot_email'] = [
+      'name' => $this->t('EBS snapshot email'),
+      'description' => $this->t('Tokens related to individual EBS snapshot email.'),
+      'needs-data' => 'aws_cloud_snapshot_email',
+    ];
+    $snapshot_email['snapshots'] = [
+      'name' => $this->t('List of EBS snapshots'),
+      'description' => $this->t('List of EBS snapshots to display to user.'),
+    ];
+    $types['aws_cloud_elastic_ip_email'] = [
+      'name' => $this->t('Elastic IP email'),
+      'description' => $this->t('Tokens related to individual Elastic IP email.'),
+      'needs-data' => 'aws_cloud_elastic_ip_email',
+    ];
+    $elastic_ip_email['elastic_ips'] = [
+      'name' => $this->t('List of Elastic IPs'),
+      'description' => $this->t('List of Elastic IPs to display to a user.'),
+    ];
+    $types['aws_cloud_launch_template_email'] = [
+      'name' => $this->t('Launch template email'),
+      'description' => $this->t('Tokens related to individual launch template email.'),
+      'needs-data' => 'aws_cloud_launch_template_email',
+    ];
+    $launch_template_email['launch_templates'] = [
+      'name' => $this->t('List of launch templates'),
+      'description' => $this->t('List of launch templates to display to a user.'),
+    ];
+    $types['aws_cloud_launch_template_request_email'] = [
+      'name' => $this->t('Launch template request email'),
+      'description' => $this->t('Tokens related to individual launch template request email.'),
+      'needs-data' => 'aws_cloud_launch_template_request_email',
+    ];
+    $launch_template_request_email['launch_templates_request'] = [
+      'name' => $this->t('List of request launch templates'),
+      'description' => $this->t('List of request launch templates to display to a user.'),
+    ];
+    $types['aws_cloud_launch_template_requext'] = [
+      'name' => $this->t('Launch template request'),
+      'description' => $this->t('Tokens related to individual launch template.'),
+      'needs-data' => 'aws_cloud_launch_template_request',
+    ];
+    $launch_template_request['name'] = [
+      'name' => $this->t('List of launch template name'),
+      'description' => $this->t('Enter the name of the launch template entity.'),
+    ];
+    $launch_template_request['launch_template_link'] = [
+      'name' => $this->t('Launch template link'),
+      'description' => $this->t('An absolute link to launch template.'),
+    ];
+    $launch_template_request['launch_template_edit_link'] = [
+      'name' => $this->t('Launch template edit link'),
+      'description' => $this->t('An absolute link to edit the launch template.'),
+    ];
+    $launch_template_request['changed'] = [
+      'name' => $this->t('Change date'),
+      'description' => $this->t('The launch template change date.'),
+    ];
+    $launch_template_request['launch_template_button_approve'] = [
+      'name' => $this->t('Approve button'),
+      'description' => $this->t('The launch template approve button.'),
+    ];
+    return [
+      'types' => $types,
+      'tokens' => [
+        'aws_cloud_instance' => $instance,
+        'aws_cloud_volume' => $volume,
+        'aws_cloud_snapshot' => $snapshot,
+        'aws_cloud_elastic_ip' => $elastic_ip,
+        'aws_cloud_launch_template' => $launch_template,
+        'aws_cloud_instance_email' => $instance_email,
+        'aws_cloud_volume_email' => $volume_email,
+        'aws_cloud_snapshot_email' => $snapshot_email,
+        'aws_cloud_elastic_ip_email' => $elastic_ip_email,
+        'aws_cloud_launch_template_email' => $launch_template_email,
+        'aws_cloud_launch_template_request' => $launch_template_request,
+        'aws_cloud_launch_template_request_email' => $launch_template_request_email,
+      ],
+    ];
+  }
+
+  /**
+   * Implements hook_tokens().
+   */
+  #[Hook('tokens')]
+  public static function tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata): array {
+    $replacements = [];
+    if ($type === 'aws_cloud_instance' && !empty($data['aws_cloud_instance'])) {
+      $replacements = aws_cloud_instance_tokens($tokens, $data);
+    }
+    elseif ($type === 'aws_cloud_volume' && !empty($data['aws_cloud_volume'])) {
+      $replacements = aws_cloud_volume_tokens($tokens, $data);
+    }
+    elseif ($type === 'aws_cloud_snapshot' && !empty($data['aws_cloud_snapshot'])) {
+      $replacements = aws_cloud_snapshot_tokens($tokens, $data);
+    }
+    elseif ($type === 'aws_cloud_elastic_ip' && !empty($data['aws_cloud_elastic_ip'])) {
+      $replacements = aws_cloud_elastic_ip_tokens($tokens, $data);
+    }
+    elseif ($type === 'aws_cloud_launch_template' && !empty($data['aws_cloud_launch_template'])) {
+      $replacements = aws_cloud_launch_template_tokens($tokens, $data);
+    }
+    elseif ($type === 'aws_cloud_launch_template_request' && !empty($data['aws_cloud_launch_template_request'])) {
+      $replacements = aws_cloud_launch_template_request_tokens($tokens, $data);
+    }
+    elseif ($type === 'aws_cloud_instance_email') {
+      $replacements = aws_cloud_instance_email_tokens($tokens, $data);
+    }
+    elseif ($type === 'aws_cloud_volume_email') {
+      $replacements = aws_cloud_volume_email_tokens($tokens, $data);
+    }
+    elseif ($type === 'aws_cloud_snapshot_email') {
+      $replacements = aws_cloud_snapshot_email_tokens($tokens, $data);
+    }
+    elseif ($type === 'aws_cloud_elastic_ip_email') {
+      $replacements = aws_cloud_elastic_ip_email_tokens($tokens, $data);
+    }
+    elseif ($type === 'aws_cloud_launch_template_email') {
+      $replacements = aws_cloud_launch_template_email_tokens($tokens, $data);
+    }
+    elseif ($type === 'aws_cloud_launch_template_request_email') {
+      $replacements = aws_cloud_launch_template_request_email_tokens($tokens, $data);
+    }
+    return $replacements;
+  }
+
+}
diff --git a/modules/cloud_service_providers/cloud_cluster/cloud_cluster.module b/modules/cloud_service_providers/cloud_cluster/cloud_cluster.module
index d03f907..a49f10f 100644
--- a/modules/cloud_service_providers/cloud_cluster/cloud_cluster.module
+++ b/modules/cloud_service_providers/cloud_cluster/cloud_cluster.module
@@ -7,6 +7,8 @@
  * This module handles UI interactions with the cloud system for Cloud Cluster.
  */
 
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\cloud_cluster\Hook\CloudClusterHooks;
 use Drupal\Component\Serialization\Yaml;
 use Drupal\Core\Ajax\AjaxResponse;
 use Drupal\Core\Ajax\CssCommand;
@@ -16,15 +18,11 @@ use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
-use Drupal\Core\Field\BaseFieldDefinition;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Render\AttachmentsInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
-use Drupal\Core\Url;
 use Drupal\cloud\Entity\CloudConfig;
 use Drupal\cloud\Entity\CloudLaunchTemplate;
-use Drupal\cloud_cluster\Entity\CloudClusterWorker\CloudClusterWorker;
-use Drupal\cloud_cluster\Plugin\rest\resource\CloudClusterWorkerResource;
 use Drupal\cloud_cluster\Service\CloudClusterServiceInterface;
 use Drupal\field\Entity\FieldStorageConfig;
 use Drupal\file\Entity\File;
@@ -34,44 +32,25 @@ use Drupal\views\ViewExecutable;
 /**
  * Implements hook_help().
  */
+#[LegacyHook]
 function cloud_cluster_help($route_name, RouteMatchInterface $route_match): string {
-  $output = '';
-  switch ($route_name) {
-    case 'help.page.cloud_cluster':
-      $output = '<h3>' . t('About') . '</h3>';
-      $output .= '<ul>';
-      $output .= '<li>' . \Drupal::service('cloud')->getVersion() . '</li>';
-      $output .= '<li>' . t('This module creates a user interface for managing Cloud Orchestrator (Cloud Cluster).') . '</li>';
-      $output .= '</ul>';
-      $output .= '<h3>' . t('Features') . '</h3>';
-      $output .= '<ul>';
-      $output .= '<li>' . t('<strong>Cloud Orchestrator</strong>');
-      $output .= '<ul>';
-      $output .= '<li>' . t('Manage Cloud Orchestrator.') . '</li>';
-      $output .= '</ul></li></ul>';
-      $output .= '<p>' . t('For more information, see the <a href=":cloud_documentation">online documentation for the Cloud Orchestrator module</a>.', [':cloud_documentation' => 'https://www.drupal.org/docs/8/modules/cloud']) . '</p>';
-  }
-
-  return $output;
+  return \Drupal::service(CloudClusterHooks::class)->help($route_name, $route_match);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function cloud_cluster_form_cloud_config_cloud_cluster_edit_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  $form['field_cloud_cluster_api_token']['widget'][0]['#disabled'] = TRUE;
-  cloud_cluster_form_cloud_config_cloud_cluster_form_common_alter($form, $form_state, $form_id);
+  \Drupal::service(CloudClusterHooks::class)->formCloudConfigCloudClusterEditFormAlter($form, $form_state, $form_id);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function cloud_cluster_form_cloud_config_cloud_cluster_add_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  $form['cloud_context']['#access'] = FALSE;
-  $form['field_cloud_cluster_api_token']['#access'] = FALSE;
-  $form['actions']['submit']['#submit'] = ['cloud_cluster_form_cloud_config_cloud_cluster_add_form_submit'];
-
-  cloud_cluster_form_cloud_config_cloud_cluster_form_common_alter($form, $form_state, $form_id);
+  \Drupal::service(CloudClusterHooks::class)->formCloudConfigCloudClusterAddFormAlter($form, $form_state, $form_id);
 }
 
 /**
@@ -153,60 +132,33 @@ function cloud_cluster_form_cloud_config_cloud_cluster_form_common_alter(array &
  *
  * Hook for form cloud_admin_settings.
  */
+#[LegacyHook]
 function cloud_cluster_form_cloud_admin_settings_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  $config = \Drupal::configFactory()->get('cloud_cluster.settings');
-
-  $form['icon'] = [
-    '#type' => 'details',
-    '#title' => t('Icon'),
-    '#open' => TRUE,
-  ];
-
-  $form['icon']['cloud_cluster_cloud_config_icon'] = [
-    '#type' => 'managed_file',
-    '#title' => t('Cloud Orchestrator cloud service provider icon'),
-    '#default_value' => [
-      'fids' => $config->get('cloud_cluster_cloud_config_icon'),
-    ],
-    '#description' => t('Upload an image to represent Cloud Orchestrator.'),
-    '#upload_location' => 'public://images/cloud/icons',
-    '#upload_validators' => [
-      'file_validate_is_image' => [],
-    ],
-  ];
+  \Drupal::service(CloudClusterHooks::class)->formCloudAdminSettingsAlter($form, $form_state, $form_id);
 }
 
 /**
  * Implements hook_default_cloud_config_icon().
  */
+#[LegacyHook]
 function cloud_cluster_default_cloud_config_icon($entity): ?int {
-  // Provides the calling hook with the default Cloud Orchestrator icon.
-  if ($entity->bundle() !== 'cloud_cluster') {
-    return NULL;
-  }
-
-  $config = \Drupal::config('cloud_cluster.settings');
-  return $config->get('cloud_cluster_cloud_config_icon');
+  return \Drupal::service(CloudClusterHooks::class)->defaultCloudConfigIcon($entity);
 }
 
 /**
  * Implements hook_cloud_config_predelete().
  */
+#[LegacyHook]
 function cloud_cluster_cloud_config_predelete(CloudConfig $cloud_config): void {
-  if ($cloud_config->bundle() === 'cloud_cluster') {
-    \Drupal::service('cloud')->deleteQueue('cloud_cluster_sync_resources_queue');
-  }
+  \Drupal::service(CloudClusterHooks::class)->cloudConfigPredelete($cloud_config);
 }
 
 /**
  * Implements hook_cloud_config_delete().
  */
+#[LegacyHook]
 function cloud_cluster_cloud_config_delete(CloudConfig $cloud_config): void {
-  if ($cloud_config->bundle() === 'cloud_cluster') {
-    // Clear cache cannot happen in hook_cloud_config_predelete().
-    // Moving it into hook_cloud_config_delete().
-    \Drupal::service('cloud')->clearAllCacheValues();
-  }
+  \Drupal::service(CloudClusterHooks::class)->cloudConfigDelete($cloud_config);
 }
 
 /**
@@ -267,159 +219,49 @@ function cloud_cluster_cloud_config_fieldsets(array &$fields): void {
 /**
  * Implements hook_preprocess_page().
  */
+#[LegacyHook]
 function cloud_cluster_preprocess_page(array &$variables): void {
-  $route = \Drupal::routeMatch()->getRouteObject();
-  if (empty($route)) {
-    return;
-  }
-
-  $view_id = $route->getDefault('view_id');
-  if ($view_id === 'cloud_cluster_worker') {
-    $block_manager = \Drupal::service('plugin.manager.block');
-    $plugin_block = $block_manager->createInstance('cloud_cluster_worker_location', []);
-    $render = $plugin_block->build();
-    $variables['page']['content'] = [$render, $variables['page']['content']];
-  }
+  \Drupal::service(CloudClusterHooks::class)->preprocessPage($variables);
 }
 
 /**
  * Implements hook_ENTITY_TYPE_view_alter().
  */
+#[LegacyHook]
 function cloud_cluster_cloud_config_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
-  if ($entity->bundle() === 'cloud_cluster') {
-    $map_json_url = \Drupal::config('cloud.settings')->get('cloud_custom_location_map_json_url');
-    $url = Url::fromRoute('entity.cloud_config.location', ['cloud_config' => $entity->id()])
-      ->toString();
-
-    $build['cloud_config_location_map'] = [
-      '#markup' => '<div id="cloud_config_location"></div>',
-      '#attached' => [
-        'library' => [
-          'cloud/cloud_config_location',
-        ],
-        'drupalSettings' => [
-          'cloud' => [
-            'cloud_location_map_json_url' => $map_json_url,
-            'cloud_config_location_json_url' => $url,
-          ],
-        ],
-      ],
-    ];
-
-    $build['field_location_country']['#access'] = FALSE;
-    $build['field_location_city']['#access'] = FALSE;
-    $build['field_location_longitude']['#access'] = FALSE;
-    $build['field_location_latitude']['#access'] = FALSE;
-
-    cloud_cluster_cloud_config_fieldsets($build);
-  }
+  \Drupal::service(CloudClusterHooks::class)->cloudConfigViewAlter($build, $entity, $display);
 }
 
 /**
  * Implements hook_rest_resource_alter().
  */
+#[LegacyHook]
 function cloud_cluster_rest_resource_alter(array &$definitions): void {
-  $definitions['entity:cloud_cluster_worker']['class'] = CloudClusterWorkerResource::class;
+  \Drupal::service(CloudClusterHooks::class)->restResourceAlter($definitions);
 }
 
 /**
  * Implements hook_entity_base_field_info().
  */
+#[LegacyHook]
 function cloud_cluster_entity_base_field_info(EntityTypeInterface $entity_type): array {
-  if (!cloud_cluster_is_entity_type_supported($entity_type->id())) {
-    return [];
-  }
-
-  $fields = [];
-  $fields['cloud_cluster_name'] = BaseFieldDefinition::create('string')
-    ->setLabel(t('Cloud Orchestrator name'))
-    ->setDescription(t('The name of cloud orchestrator.'));
-
-  $fields['cloud_cluster_worker_name'] = BaseFieldDefinition::create('string')
-    ->setLabel(t('Cloud Orchestrator worker name'))
-    ->setDescription(t('The name of cloud orchestrator worker.'));
-
-  return $fields;
+  return \Drupal::service(CloudClusterHooks::class)->entityBaseFieldInfo($entity_type);
 }
 
 /**
  * Implements hook_views_query_alter().
  */
+#[LegacyHook]
 function cloud_cluster_views_query_alter(ViewExecutable $view, QueryPluginBase $query) {
-  if ($view->id() !== 'cloud_config') {
-    return;
-  }
-
-  $route_match = \Drupal::service('current_route_match');
-  if ($route_match->getRouteName() === 'views.ajax') {
-    // When the request is from ajax, get the cloud context from referer.
-    global $base_url;
-    // Get the referer URL.
-    $referer = \Drupal::request()->headers->get('referer');
-    if (!empty($referer)) {
-      // Get the alias or the referer.
-      $alias = substr($referer, strlen($base_url));
-      $url = Url::fromUri('internal:' . $alias);
-      $params = $url->getRouteParameters();
-
-      $cloud_context = $params['cloud_context'] ?? NULL;
-      $cloud_cluster_worker_id = $params['cloud_cluster_worker'] ?? NULL;
-
-      if (empty($cloud_context) || empty($cloud_cluster_worker_id)) {
-        return;
-      }
-
-      $cloud_cluster_worker = CloudClusterWorker::load($cloud_cluster_worker_id);
-    }
-  }
-  else {
-    $cloud_context = $route_match->getParameter('cloud_context');
-    $cloud_cluster_worker = $route_match->getParameter('cloud_cluster_worker');
-  }
-
-  if (empty($cloud_context) || empty($cloud_cluster_worker)) {
-    return;
-  }
-
-  $query->addWhere(0, 'cloud_cluster_name', $cloud_context);
-  $query->addWhere(0, 'cloud_cluster_worker_name', $cloud_cluster_worker->getName());
+  \Drupal::service(CloudClusterHooks::class)->viewsQueryAlter($view, $query);
 }
 
 /**
  * Implements hook_menu_local_tasks_alter().
  */
+#[LegacyHook]
 function cloud_cluster_menu_local_tasks_alter(&$data, $route_name): void {
-  if ($route_name === 'cloud_cluster.view.cloud_config.list' || strpos($route_name, 'cloud_cluster.view.') !== 0) {
-    return;
-  }
-
-  $cloud_route_name = substr($route_name, strlen('cloud_cluster.'));
-
-  $route_match = \Drupal::service('current_route_match');
-  $cloud_context = $route_match->getParameter('cloud_context');
-  $cloud_cluster_name = $route_match->getParameter('cloud_cluster_name');
-  $cloud_cluster_worker = $route_match->getParameter('cloud_cluster_worker');
-
-  $local_tasks = \Drupal::service('plugin.manager.menu.local_task')->getLocalTasksForRoute($cloud_route_name);
-  foreach ($local_tasks[0] ?: [] as $name => $local_task) {
-    if ($name === 'aws_cloud.local_tasks.instance_type_price') {
-      continue;
-    }
-
-    $data['tabs'][0]['cloud_cluster.' . $name] = [
-      '#theme' => 'menu_local_task',
-      '#link' => [
-        'title' => $local_task->getTitle(),
-        'url' => Url::fromRoute('cloud_cluster.' . $local_task->getRouteName(), [
-          'cloud_context' => $cloud_context,
-          'cloud_cluster_name' => $cloud_cluster_name,
-          'cloud_cluster_worker' => $cloud_cluster_worker->id(),
-        ]),
-      ],
-      '#active' => $route_match->getRouteName() === 'cloud_cluster.' . $local_task->getRouteName(),
-    ];
-  }
-
+  \Drupal::service(CloudClusterHooks::class)->menuLocalTasksAlter($data, $route_name);
 }
 
 /**
@@ -440,31 +282,25 @@ function cloud_cluster_is_entity_type_supported($entity_type): bool {
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function cloud_cluster_form_cloud_launch_template_cloud_cluster_edit_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  cloud_cluster_form_cloud_launch_template_cloud_cluster_form_common_alter($form, $form_state, $form_id);
+  \Drupal::service(CloudClusterHooks::class)->formCloudLaunchTemplateCloudClusterEditFormAlter($form, $form_state, $form_id);
 }
 
 /**
  * Implements hook_form_BASE_FORM_ID_alter().
  */
+#[LegacyHook]
 function cloud_cluster_form_cloud_launch_template_cloud_cluster_add_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  cloud_cluster_form_cloud_launch_template_cloud_cluster_form_common_alter($form, $form_state, $form_id);
+  \Drupal::service(CloudClusterHooks::class)->formCloudLaunchTemplateCloudClusterAddFormAlter($form, $form_state, $form_id);
 }
 
 /**
  * Implements hook_entity_view_alter().
  */
+#[LegacyHook]
 function cloud_cluster_entity_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display): void {
-  if ($entity->getEntityTypeId() !== 'cloud_launch_template'
-    || $entity->bundle() !== 'cloud_cluster'
-  ) {
-    return;
-  }
-
-  \Drupal::service('cloud')->reorderForm(
-    $build,
-    cloud_cluster_launch_template_field_orders()
-  );
+  \Drupal::service(CloudClusterHooks::class)->entityViewAlter($build, $entity, $display);
 }
 
 /**
@@ -649,61 +485,9 @@ function cloud_cluster_get_deployment_templates_by_type(?string $deployment_type
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function cloud_cluster_form_cloud_launch_template_cloud_cluster_launch_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  $form['#validate'][] = 'cloud_cluster_form_cloud_launch_template_cloud_cluster_launch_form_validate';
-
-  /** @var \Drupal\cloud\Entity\CloudLaunchTemplate $cloud_launch_template */
-  $cloud_launch_template = \Drupal::routeMatch()->getParameter('cloud_launch_template');
-
-  $entities = \Drupal::service('plugin.manager.cloud_config_plugin')->loadConfigEntities($cloud_launch_template->field_deployment_type->value);
-  $cloud_contexts = [];
-  $account = \Drupal::currentUser();
-  foreach ($entities ?: [] as $entity) {
-    if ($account->hasPermission('view all cloud service providers') || $account->hasPermission('view ' . $entity->getCloudContext())) {
-      $cloud_contexts[$entity->getCloudContext()] = $entity->label();
-    }
-  }
-
-  $form['site_name'] = [
-    '#type' => 'textfield',
-    '#title' => t('Name'),
-    '#required' => TRUE,
-  ];
-
-  $form['cloud_service_provider'] = [
-    '#type' => 'details',
-    '#title' => t('Deployment provider'),
-    '#open' => TRUE,
-  ];
-
-  $cloud_context = array_keys($cloud_contexts)[0];
-  $form['cloud_service_provider']['target_provider'] = [
-    '#type' => 'select',
-    '#title' => t('Target provider'),
-    '#options' => $cloud_contexts,
-    '#default_value' => $cloud_context,
-    '#required' => TRUE,
-    '#ajax' => [
-      'callback' => 'cloud_cluster_target_provider_ajax_callback',
-      'event' => 'change',
-      'wrapper' => 'deployment-parameters-container',
-      'progress' => [
-        'type' => 'throbber',
-        'message' => t('Retrieving...'),
-      ],
-    ],
-  ];
-
-  if (!empty($form_state->getValue('target_provider'))) {
-    $cloud_context = $form_state->getValue('target_provider');
-  }
-
-  cloud_cluster_render_deployment_parameters($form, $cloud_launch_template, $cloud_context);
-
-  $view_builder = \Drupal::entityTypeManager()->getViewBuilder('cloud_launch_template');
-  $build = $view_builder->view($cloud_launch_template, 'view');
-  unset($build['#weight']);
-  $form['detail'] = $build;
+  \Drupal::service(CloudClusterHooks::class)->formCloudLaunchTemplateCloudClusterLaunchFormAlter($form, $form_state, $form_id);
 }
 
 /**
@@ -812,44 +596,7 @@ function cloud_cluster_render_deployment_parameters(array &$form, CloudLaunchTem
 /**
  * Implements hook_cron().
  */
+#[LegacyHook]
 function cloud_cluster_cron(): void {
-  $sites = \Drupal::entityTypeManager()
-    ->getStorage('cloud_cluster_site')
-    ->loadByProperties([]);
-  foreach ($sites ?: [] as $site) {
-    if (!empty($site->getUrl())) {
-      continue;
-    }
-
-    $resource_entities = [];
-    foreach ($site->getCloudResources() ?: [] as $resource) {
-      $resource_entity = \Drupal::entityTypeManager()
-        ->getStorage($resource['item_key'])
-        ->load($resource['item_value']);
-      if (empty($resource_entity)) {
-        continue;
-      }
-
-      $resource_entities[] = $resource_entity;
-    }
-
-    $deployment_type = $site->getCloudLaunchTemplate()->field_deployment_type->value;
-    $service_name = "$deployment_type.cloud_orchestrator_manager";
-    if (!\Drupal::hasService($service_name)) {
-      $message = t('The cloud orchestrator manager for %type could not be found.', [
-        '%type' => $deployment_type,
-      ]);
-      \Drupal::messenger()->addError($message);
-      continue;
-    }
-
-    $endpoint = \Drupal::service($service_name)->getEndpoint($resource_entities);
-    if (empty($endpoint)) {
-      continue;
-    }
-
-    $site->setUrl($endpoint);
-    $site->setStatus('Deployed');
-    $site->save();
-  }
+  \Drupal::service(CloudClusterHooks::class)->cron();
 }
diff --git a/modules/cloud_service_providers/cloud_cluster/cloud_cluster.services.yml b/modules/cloud_service_providers/cloud_cluster/cloud_cluster.services.yml
index e520b66..589bce1 100644
--- a/modules/cloud_service_providers/cloud_cluster/cloud_cluster.services.yml
+++ b/modules/cloud_service_providers/cloud_cluster/cloud_cluster.services.yml
@@ -8,3 +8,7 @@ services:
     class: Drupal\cloud_cluster\Routing\CloudClusterRouteSubscriber
     tags:
       - { name: event_subscriber }
+
+  Drupal\cloud_cluster\Hook\CloudClusterHooks:
+    class: Drupal\cloud_cluster\Hook\CloudClusterHooks
+    autowire: true
diff --git a/modules/cloud_service_providers/cloud_cluster/src/Hook/CloudClusterHooks.php b/modules/cloud_service_providers/cloud_cluster/src/Hook/CloudClusterHooks.php
new file mode 100644
index 0000000..c6c0ceb
--- /dev/null
+++ b/modules/cloud_service_providers/cloud_cluster/src/Hook/CloudClusterHooks.php
@@ -0,0 +1,399 @@
+<?php
+
+namespace Drupal\cloud_cluster\Hook;
+
+use Drupal\cloud_cluster\Entity\CloudClusterWorker\CloudClusterWorker;
+use Drupal\views\Plugin\views\query\QueryPluginBase;
+use Drupal\views\ViewExecutable;
+use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\cloud_cluster\Plugin\rest\resource\CloudClusterWorkerResource;
+use Drupal\Core\Url;
+use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\cloud\Entity\CloudConfig;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for cloud_cluster.
+ */
+class CloudClusterHooks {
+  use StringTranslationTrait;
+
+  /**
+   * Implements hook_help().
+   */
+  #[Hook('help')]
+  public function help($route_name, RouteMatchInterface $route_match): string {
+    $output = '';
+    switch ($route_name) {
+      case 'help.page.cloud_cluster':
+        $output = '<h3>' . $this->t('About') . '</h3>';
+        $output .= '<ul>';
+        $output .= '<li>' . \Drupal::service('cloud')->getVersion() . '</li>';
+        $output .= '<li>' . $this->t('This module creates a user interface for managing Cloud Orchestrator (Cloud Cluster).') . '</li>';
+        $output .= '</ul>';
+        $output .= '<h3>' . $this->t('Features') . '</h3>';
+        $output .= '<ul>';
+        $output .= '<li>' . $this->t('<strong>Cloud Orchestrator</strong>');
+        $output .= '<ul>';
+        $output .= '<li>' . $this->t('Manage Cloud Orchestrator.') . '</li>';
+        $output .= '</ul></li></ul>';
+        $output .= '<p>' . $this->t('For more information, see the <a href=":cloud_documentation">online documentation for the Cloud Orchestrator module</a>.', [
+          ':cloud_documentation' => 'https://www.drupal.org/docs/8/modules/cloud',
+        ]) . '</p>';
+    }
+    return $output;
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_config_cloud_cluster_edit_form_alter')]
+  public static function formCloudConfigCloudClusterEditFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    $form['field_cloud_cluster_api_token']['widget'][0]['#disabled'] = TRUE;
+    cloud_cluster_form_cloud_config_cloud_cluster_form_common_alter($form, $form_state, $form_id);
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_config_cloud_cluster_add_form_alter')]
+  public static function formCloudConfigCloudClusterAddFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    $form['cloud_context']['#access'] = FALSE;
+    $form['field_cloud_cluster_api_token']['#access'] = FALSE;
+    $form['actions']['submit']['#submit'] = [
+      'cloud_cluster_form_cloud_config_cloud_cluster_add_form_submit',
+    ];
+    cloud_cluster_form_cloud_config_cloud_cluster_form_common_alter($form, $form_state, $form_id);
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   *
+   * Hook for form cloud_admin_settings.
+   */
+  #[Hook('form_cloud_admin_settings_alter')]
+  public function formCloudAdminSettingsAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    $config = \Drupal::configFactory()->get('cloud_cluster.settings');
+    $form['icon'] = [
+      '#type' => 'details',
+      '#title' => $this->t('Icon'),
+      '#open' => TRUE,
+    ];
+    $form['icon']['cloud_cluster_cloud_config_icon'] = [
+      '#type' => 'managed_file',
+      '#title' => $this->t('Cloud Orchestrator cloud service provider icon'),
+      '#default_value' => [
+        'fids' => $config->get('cloud_cluster_cloud_config_icon'),
+      ],
+      '#description' => $this->t('Upload an image to represent Cloud Orchestrator.'),
+      '#upload_location' => 'public://images/cloud/icons',
+      '#upload_validators' => [
+        'file_validate_is_image' => [],
+      ],
+    ];
+  }
+
+  /**
+   * Implements hook_default_cloud_config_icon().
+   */
+  #[Hook('default_cloud_config_icon')]
+  public static function defaultCloudConfigIcon($entity): ?int {
+    // Provides the calling hook with the default Cloud Orchestrator icon.
+    if ($entity->bundle() !== 'cloud_cluster') {
+      return NULL;
+    }
+    $config = \Drupal::config('cloud_cluster.settings');
+    return $config->get('cloud_cluster_cloud_config_icon');
+  }
+
+  /**
+   * Implements hook_cloud_config_predelete().
+   */
+  #[Hook('cloud_config_predelete')]
+  public static function cloudConfigPredelete(CloudConfig $cloud_config): void {
+    if ($cloud_config->bundle() === 'cloud_cluster') {
+      \Drupal::service('cloud')->deleteQueue('cloud_cluster_sync_resources_queue');
+    }
+  }
+
+  /**
+   * Implements hook_cloud_config_delete().
+   */
+  #[Hook('cloud_config_delete')]
+  public static function cloudConfigDelete(CloudConfig $cloud_config): void {
+    if ($cloud_config->bundle() === 'cloud_cluster') {
+      // Clear cache cannot happen in hook_cloud_config_predelete().
+      // Moving it into hook_cloud_config_delete().
+      \Drupal::service('cloud')->clearAllCacheValues();
+    }
+  }
+
+  /**
+   * Implements hook_preprocess_page().
+   */
+  #[Hook('preprocess_page')]
+  public static function preprocessPage(array &$variables): void {
+    $route = \Drupal::routeMatch()->getRouteObject();
+    if (empty($route)) {
+      return;
+    }
+    $view_id = $route->getDefault('view_id');
+    if ($view_id === 'cloud_cluster_worker') {
+      $block_manager = \Drupal::service('plugin.manager.block');
+      $plugin_block = $block_manager->createInstance('cloud_cluster_worker_location', []);
+      $render = $plugin_block->build();
+      $variables['page']['content'] = [
+        $render,
+        $variables['page']['content'],
+      ];
+    }
+  }
+
+  /**
+   * Implements hook_ENTITY_TYPE_view_alter().
+   */
+  #[Hook('cloud_config_view_alter')]
+  public static function cloudConfigViewAlter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
+    if ($entity->bundle() === 'cloud_cluster') {
+      $map_json_url = \Drupal::config('cloud.settings')->get('cloud_custom_location_map_json_url');
+      $url = Url::fromRoute('entity.cloud_config.location', [
+        'cloud_config' => $entity->id(),
+      ])->toString();
+      $build['cloud_config_location_map'] = [
+        '#markup' => '<div id="cloud_config_location"></div>',
+        '#attached' => [
+          'library' => [
+            'cloud/cloud_config_location',
+          ],
+          'drupalSettings' => [
+            'cloud' => [
+              'cloud_location_map_json_url' => $map_json_url,
+              'cloud_config_location_json_url' => $url,
+            ],
+          ],
+        ],
+      ];
+      $build['field_location_country']['#access'] = FALSE;
+      $build['field_location_city']['#access'] = FALSE;
+      $build['field_location_longitude']['#access'] = FALSE;
+      $build['field_location_latitude']['#access'] = FALSE;
+      cloud_cluster_cloud_config_fieldsets($build);
+    }
+  }
+
+  /**
+   * Implements hook_rest_resource_alter().
+   */
+  #[Hook('rest_resource_alter')]
+  public static function restResourceAlter(array &$definitions): void {
+    $definitions['entity:cloud_cluster_worker']['class'] = CloudClusterWorkerResource::class;
+  }
+
+  /**
+   * Implements hook_entity_base_field_info().
+   */
+  #[Hook('entity_base_field_info')]
+  public function entityBaseFieldInfo(EntityTypeInterface $entity_type): array {
+    if (!cloud_cluster_is_entity_type_supported($entity_type->id())) {
+      return [];
+    }
+    $fields = [];
+    $fields['cloud_cluster_name'] = BaseFieldDefinition::create('string')->setLabel($this->t('Cloud Orchestrator name'))->setDescription($this->t('The name of cloud orchestrator.'));
+    $fields['cloud_cluster_worker_name'] = BaseFieldDefinition::create('string')->setLabel($this->t('Cloud Orchestrator worker name'))->setDescription($this->t('The name of cloud orchestrator worker.'));
+    return $fields;
+  }
+
+  /**
+   * Implements hook_views_query_alter().
+   */
+  #[Hook('views_query_alter')]
+  public static function viewsQueryAlter(ViewExecutable $view, QueryPluginBase $query) {
+    if ($view->id() !== 'cloud_config') {
+      return;
+    }
+    $route_match = \Drupal::service('current_route_match');
+    if ($route_match->getRouteName() === 'views.ajax') {
+      // When the request is from ajax, get the cloud context from referer.
+      global $base_url;
+      // Get the referer URL.
+      $referer = \Drupal::request()->headers->get('referer');
+      if (!empty($referer)) {
+        // Get the alias or the referer.
+        $alias = substr($referer, strlen($base_url));
+        $url = Url::fromUri('internal:' . $alias);
+        $params = $url->getRouteParameters();
+        $cloud_context = $params['cloud_context'] ?? NULL;
+        $cloud_cluster_worker_id = $params['cloud_cluster_worker'] ?? NULL;
+        if (empty($cloud_context) || empty($cloud_cluster_worker_id)) {
+          return;
+        }
+        $cloud_cluster_worker = CloudClusterWorker::load($cloud_cluster_worker_id);
+      }
+    }
+    else {
+      $cloud_context = $route_match->getParameter('cloud_context');
+      $cloud_cluster_worker = $route_match->getParameter('cloud_cluster_worker');
+    }
+    if (empty($cloud_context) || empty($cloud_cluster_worker)) {
+      return;
+    }
+    $query->addWhere(0, 'cloud_cluster_name', $cloud_context);
+    $query->addWhere(0, 'cloud_cluster_worker_name', $cloud_cluster_worker->getName());
+  }
+
+  /**
+   * Implements hook_menu_local_tasks_alter().
+   */
+  #[Hook('menu_local_tasks_alter')]
+  public static function menuLocalTasksAlter(&$data, $route_name): void {
+    if ($route_name === 'cloud_cluster.view.cloud_config.list' || strpos($route_name, 'cloud_cluster.view.') !== 0) {
+      return;
+    }
+    $cloud_route_name = substr($route_name, strlen('cloud_cluster.'));
+    $route_match = \Drupal::service('current_route_match');
+    $cloud_context = $route_match->getParameter('cloud_context');
+    $cloud_cluster_name = $route_match->getParameter('cloud_cluster_name');
+    $cloud_cluster_worker = $route_match->getParameter('cloud_cluster_worker');
+    $local_tasks = \Drupal::service('plugin.manager.menu.local_task')->getLocalTasksForRoute($cloud_route_name);
+    foreach ($local_tasks[0] ?: [] as $name => $local_task) {
+      if ($name === 'aws_cloud.local_tasks.instance_type_price') {
+        continue;
+      }
+      $data['tabs'][0]['cloud_cluster.' . $name] = [
+        '#theme' => 'menu_local_task',
+        '#link' => [
+          'title' => $local_task->getTitle(),
+          'url' => Url::fromRoute('cloud_cluster.' . $local_task->getRouteName(), [
+            'cloud_context' => $cloud_context,
+            'cloud_cluster_name' => $cloud_cluster_name,
+            'cloud_cluster_worker' => $cloud_cluster_worker->id(),
+          ]),
+        ],
+        '#active' => $route_match->getRouteName() === 'cloud_cluster.' . $local_task->getRouteName(),
+      ];
+    }
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_launch_template_cloud_cluster_edit_form_alter')]
+  public static function formCloudLaunchTemplateCloudClusterEditFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    cloud_cluster_form_cloud_launch_template_cloud_cluster_form_common_alter($form, $form_state, $form_id);
+  }
+
+  /**
+   * Implements hook_form_BASE_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_launch_template_cloud_cluster_add_form_alter')]
+  public static function formCloudLaunchTemplateCloudClusterAddFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    cloud_cluster_form_cloud_launch_template_cloud_cluster_form_common_alter($form, $form_state, $form_id);
+  }
+
+  /**
+   * Implements hook_entity_view_alter().
+   */
+  #[Hook('entity_view_alter')]
+  public static function entityViewAlter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display): void {
+    if ($entity->getEntityTypeId() !== 'cloud_launch_template' || $entity->bundle() !== 'cloud_cluster') {
+      return;
+    }
+    \Drupal::service('cloud')->reorderForm($build, cloud_cluster_launch_template_field_orders());
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_launch_template_cloud_cluster_launch_form_alter')]
+  public function formCloudLaunchTemplateCloudClusterLaunchFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    $form['#validate'][] = 'cloud_cluster_form_cloud_launch_template_cloud_cluster_launch_form_validate';
+    /** @var \Drupal\cloud\Entity\CloudLaunchTemplate $cloud_launch_template */
+    $cloud_launch_template = \Drupal::routeMatch()->getParameter('cloud_launch_template');
+    $entities = \Drupal::service('plugin.manager.cloud_config_plugin')->loadConfigEntities($cloud_launch_template->field_deployment_type->value);
+    $cloud_contexts = [];
+    $account = \Drupal::currentUser();
+    foreach ($entities ?: [] as $entity) {
+      if ($account->hasPermission('view all cloud service providers') || $account->hasPermission('view ' . $entity->getCloudContext())) {
+        $cloud_contexts[$entity->getCloudContext()] = $entity->label();
+      }
+    }
+    $form['site_name'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Name'),
+      '#required' => TRUE,
+    ];
+    $form['cloud_service_provider'] = [
+      '#type' => 'details',
+      '#title' => $this->t('Deployment provider'),
+      '#open' => TRUE,
+    ];
+    $cloud_context = array_keys($cloud_contexts)[0];
+    $form['cloud_service_provider']['target_provider'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Target provider'),
+      '#options' => $cloud_contexts,
+      '#default_value' => $cloud_context,
+      '#required' => TRUE,
+      '#ajax' => [
+        'callback' => 'cloud_cluster_target_provider_ajax_callback',
+        'event' => 'change',
+        'wrapper' => 'deployment-parameters-container',
+        'progress' => [
+          'type' => 'throbber',
+          'message' => $this->t('Retrieving...'),
+        ],
+      ],
+    ];
+    if (!empty($form_state->getValue('target_provider'))) {
+      $cloud_context = $form_state->getValue('target_provider');
+    }
+    cloud_cluster_render_deployment_parameters($form, $cloud_launch_template, $cloud_context);
+    $view_builder = \Drupal::entityTypeManager()->getViewBuilder('cloud_launch_template');
+    $build = $view_builder->view($cloud_launch_template, 'view');
+    unset($build['#weight']);
+    $form['detail'] = $build;
+  }
+
+  /**
+   * Implements hook_cron().
+   */
+  #[Hook('cron')]
+  public function cron(): void {
+    $sites = \Drupal::entityTypeManager()->getStorage('cloud_cluster_site')->loadByProperties([]);
+    foreach ($sites ?: [] as $site) {
+      if (!empty($site->getUrl())) {
+        continue;
+      }
+      $resource_entities = [];
+      foreach ($site->getCloudResources() ?: [] as $resource) {
+        $resource_entity = \Drupal::entityTypeManager()->getStorage($resource['item_key'])->load($resource['item_value']);
+        if (empty($resource_entity)) {
+          continue;
+        }
+        $resource_entities[] = $resource_entity;
+      }
+      $deployment_type = $site->getCloudLaunchTemplate()->field_deployment_type->value;
+      $service_name = "{$deployment_type}.cloud_orchestrator_manager";
+      if (!\Drupal::hasService($service_name)) {
+        $message = $this->t('The cloud orchestrator manager for %type could not be found.', [
+          '%type' => $deployment_type,
+        ]);
+        \Drupal::messenger()->addError($message);
+        continue;
+      }
+      $endpoint = \Drupal::service($service_name)->getEndpoint($resource_entities);
+      if (empty($endpoint)) {
+        continue;
+      }
+      $site->setUrl($endpoint);
+      $site->setStatus('Deployed');
+      $site->save();
+    }
+  }
+
+}
diff --git a/modules/cloud_service_providers/docker/docker.module b/modules/cloud_service_providers/docker/docker.module
index b4633db..9648390 100644
--- a/modules/cloud_service_providers/docker/docker.module
+++ b/modules/cloud_service_providers/docker/docker.module
@@ -7,23 +7,14 @@
  * This module manage docker.
  */
 
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\docker\Hook\DockerHooks;
 use Drupal\Core\Routing\RouteMatchInterface;
 
 /**
  * Implements hook_help().
  */
+#[LegacyHook]
 function docker_help($route_name, RouteMatchInterface $route_match): string {
-  switch ($route_name) {
-    case 'help.page.docker':
-      $output = '<h3>' . t('About') . '</h3>';
-      $output .= '<ul>';
-      $output .= '<li>' . \Drupal::service('cloud')->getVersion() . '</li>';
-      $output .= '<li>' . t('The Docker module allows users to manage Docker.') . '</li>';
-      $output .= '</ul>';
-      $output .= '<p>' . t('For more information, see the <a href=":cloud_documentation">online documentation for the Docker module</a>.', [':cloud_documentation' => 'https://www.drupal.org/docs/8/modules/cloud']) . '</p>';
-      return $output;
-
-    default:
-      return '';
-  }
+  return \Drupal::service(DockerHooks::class)->help($route_name, $route_match);
 }
diff --git a/modules/cloud_service_providers/docker/docker.services.yml b/modules/cloud_service_providers/docker/docker.services.yml
index b23e457..77bd0a4 100644
--- a/modules/cloud_service_providers/docker/docker.services.yml
+++ b/modules/cloud_service_providers/docker/docker.services.yml
@@ -2,3 +2,7 @@ services:
   docker:
     class: Drupal\docker\Service\DockerService
     arguments: ['@config.factory', '@http_client']
+
+  Drupal\docker\Hook\DockerHooks:
+    class: Drupal\docker\Hook\DockerHooks
+    autowire: true
diff --git a/modules/cloud_service_providers/docker/src/Hook/DockerHooks.php b/modules/cloud_service_providers/docker/src/Hook/DockerHooks.php
new file mode 100644
index 0000000..962b1a5
--- /dev/null
+++ b/modules/cloud_service_providers/docker/src/Hook/DockerHooks.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Drupal\docker\Hook;
+
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for docker.
+ */
+class DockerHooks {
+  use StringTranslationTrait;
+
+  /**
+   * Implements hook_help().
+   */
+  #[Hook('help')]
+  public function help($route_name, RouteMatchInterface $route_match): string {
+    switch ($route_name) {
+      case 'help.page.docker':
+        $output = '<h3>' . $this->t('About') . '</h3>';
+        $output .= '<ul>';
+        $output .= '<li>' . \Drupal::service('cloud')->getVersion() . '</li>';
+        $output .= '<li>' . $this->t('The Docker module allows users to manage Docker.') . '</li>';
+        $output .= '</ul>';
+        $output .= '<p>' . $this->t('For more information, see the <a href=":cloud_documentation">online documentation for the Docker module</a>.', [
+          ':cloud_documentation' => 'https://www.drupal.org/docs/8/modules/cloud',
+        ]) . '</p>';
+        return $output;
+
+      default:
+        return '';
+    }
+  }
+
+}
diff --git a/modules/cloud_service_providers/k8s/k8s.module b/modules/cloud_service_providers/k8s/k8s.module
index 75f3871..ae4eded 100644
--- a/modules/cloud_service_providers/k8s/k8s.module
+++ b/modules/cloud_service_providers/k8s/k8s.module
@@ -7,7 +7,8 @@
  * This module handles UI interactions with the cloud system for K8s.
  */
 
-use Drupal\Component\Plugin\Exception\PluginNotFoundException;
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\k8s\Hook\K8sHooks;
 use Drupal\Component\Serialization\Yaml;
 use Drupal\Core\Ajax\AjaxResponse;
 use Drupal\Core\Ajax\ReplaceCommand;
@@ -15,15 +16,11 @@ use Drupal\Core\Database\Query\AlterableInterface;
 use Drupal\Core\Datetime\TimeZoneFormHelper;
 use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
-use Drupal\Core\Entity\Element\EntityAutocomplete;
 use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\EntityMalformedException;
-use Drupal\Core\Entity\EntityStorageException;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Link;
 use Drupal\Core\Render\AttachmentsInterface;
-use Drupal\Core\Render\Markup;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Url;
 use Drupal\cloud\Entity\CloudConfig;
@@ -31,7 +28,6 @@ use Drupal\cloud\Entity\CloudConfigInterface;
 use Drupal\cloud\Entity\CloudLaunchTemplate;
 use Drupal\cloud\Entity\CloudLaunchTemplateInterface;
 use Drupal\cloud\Service\CloudService;
-use Drupal\field\Entity\FieldConfig;
 use Drupal\field\Entity\FieldStorageConfig;
 use Drupal\file\Entity\File;
 use Drupal\k8s\Controller\K8sCostsControllerBase;
@@ -40,7 +36,6 @@ use Drupal\k8s\Entity\K8sExportableEntityInterface;
 use Drupal\k8s\Entity\K8sNamespace;
 use Drupal\k8s\Entity\K8sNode;
 use Drupal\k8s\Form\K8sContentFormInterface;
-use Drupal\k8s\Plugin\EntityReferenceSelection\LaunchProjectUserSelection;
 use Drupal\k8s\Service\K8sServiceException;
 use Drupal\k8s\Service\K8sServiceInterface;
 use Drupal\user\Entity\User;
@@ -48,92 +43,33 @@ use Drupal\user\Entity\User;
 /**
  * Implements hook_help().
  */
+#[LegacyHook]
 function k8s_help($route_name, RouteMatchInterface $route_match): string {
-  switch ($route_name) {
-    case 'help.page.k8s':
-      $output = '<h3>' . t('About') . '</h3>';
-      $output .= '<ul>';
-      $output .= '<li>' . \Drupal::service('cloud')->getVersion() . '</li>';
-      $output .= '<li>' . t('This module creates a user interface for managing Kubernetes (K8s).') . '</li>';
-      $output .= '</ul>';
-      $output .= '<h3>' . t('Features') . '</h3>';
-      $output .= '<ul>';
-      $output .= '<li>' . t('<strong>K8s</strong>');
-      $output .= '<ul>';
-      $output .= '<li>' . t('Manage K8s clusters.') . '</li>';
-      $output .= '<li>' . t('Manage most of K8s resources.') . '</li>';
-      $output .= '<li>' . t('Support multi-tenant like a K8s as a service.') . '</li>';
-      $output .= '<li>' . t('Support K8s clusters under fully closed (Internet unreachable) network.') . '</li>';
-      $output .= '</ul></li></ul>';
-      $output .= '<ul>';
-      $output .= '<li>' . t('<strong>K8s resources optimization</strong>');
-      $output .= '<ul>';
-      $output .= '<li>' . t('Integrate w/ Amazon EC2 / EKS.') . '</li>';
-      $output .= '<li>' . t('Visualize costs on EKS.') . '</li>';
-      $output .= '<li>' . t('Schedule and manage K8s resource allocation based on entire resource utilization.') . '</li>';
-      $output .= '<li>' . t('Support resource deployment under multi-cluster environment.') . '</li>';
-      $output .= '</ul></li></ul>';
-      $output .= '<p>' . t('For more information, see the <a href=":cloud_documentation">online documentation for the K8s module</a>.', [':cloud_documentation' => 'https://www.drupal.org/docs/8/modules/cloud']) . '</p>';
-      break;
-
-    default:
-      $output = '';
-  }
-  return $output;
+  return \Drupal::service(K8sHooks::class)->help($route_name, $route_match);
 }
 
 /**
  * Implements hook_cloud_config_update().
  */
+#[LegacyHook]
 function k8s_cloud_config_update(CloudConfig $cloud_config): void {
-  // Only perform resource update if coming from the cloud service provider
-  // edit form.  This hook can be triggered during user_delete().  user_delete()
-  // runs in progressive batch mode and `k8s_cloud_config_update()` will
-  // try to run a second non-progressive batch mode.  This confuses Drupal
-  // and the original batch processing will never complete, causing the
-  // site to hang.
-  if ($cloud_config->bundle() === 'k8s'
-    && \Drupal::routeMatch()->getRouteName() === 'entity.cloud_config.edit_form') {
-    // Update resources.
-    k8s_update_resources($cloud_config->getCloudContext());
-  }
+  \Drupal::service(K8sHooks::class)->cloudConfigUpdate($cloud_config);
 }
 
 /**
  * Implements hook_cloud_config_predelete().
  */
+#[LegacyHook]
 function k8s_cloud_config_predelete(CloudConfig $cloud_config): void {
-  // NOTE: Using hook_entity_predelete() to make sure the K8s CloudConfigPlugin
-  // is still available to clear out the K8s entities.
-  // This is to fix an issue using bulk operation to delete AWS and K8s
-  // cloud service providers.
-  // See https://www.drupal.org/project/cloud/issues/3186140
-  if ($cloud_config->bundle() === 'k8s') {
-
-    $cloud_context = $cloud_config->getCloudContext();
-
-    /** @var \Drupal\k8s\Service\K8sServiceInterface $k8s_service */
-    $k8s_service = \Drupal::service('k8s');
-    $k8s_service->setCloudContext($cloud_context);
-    \Drupal::service('cloud')->clearAllEntities('k8s', $cloud_context);
-
-    \Drupal::service('cloud')->deleteQueue(\Drupal::service('cloud')->getResourceQueueName($cloud_config));
-  }
-
-  // Clear resource data cache.
-  $cache_service = \Drupal::service('cloud.cache');
-  $cache_service->clearResourceDataCache($cloud_config->getCloudContext(), 'k8s_node');
-  $cache_service->clearResourceDataCache($cloud_config->getCloudContext(), 'k8s_pod');
+  \Drupal::service(K8sHooks::class)->cloudConfigPredelete($cloud_config);
 }
 
 /**
  * Implements hook_cloud_config_delete().
  */
+#[LegacyHook]
 function k8s_cloud_config_delete(CloudConfig $cloud_config): void {
-  if ($cloud_config->bundle() === 'k8s') {
-    // Make sure to delete the cache to clear out the menu system.
-    \Drupal::service('cloud')->clearAllCacheValues();
-  }
+  \Drupal::service(K8sHooks::class)->cloudConfigDelete($cloud_config);
 }
 
 /**
@@ -187,19 +123,17 @@ function k8s_update_resources($cloud_context): void {
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function k8s_form_cloud_config_k8s_edit_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  k8s_form_cloud_config_k8s_form_common_alter($form, $form_state, $form_id);
+  \Drupal::service(K8sHooks::class)->formCloudConfigK8sEditFormAlter($form, $form_state, $form_id);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function k8s_form_cloud_config_k8s_add_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  $form['cloud_context']['#access'] = FALSE;
-  $form['actions']['submit']['#submit'] = ['k8s_form_cloud_config_k8s_add_form_submit'];
-  $form['#validate'][] = 'k8s_form_cloud_config_k8s_add_form_validate';
-
-  k8s_form_cloud_config_k8s_form_common_alter($form, $form_state, $form_id);
+  \Drupal::service(K8sHooks::class)->formCloudConfigK8sAddFormAlter($form, $form_state, $form_id);
 }
 
 /**
@@ -403,18 +337,17 @@ function k8s_form_cloud_config_k8s_form_common_alter(array &$form, FormStateInte
 /**
  * Implements hook_default_cloud_config_icon().
  */
+#[LegacyHook]
 function k8s_default_cloud_config_icon(EntityInterface $entity): ?int {
-  // Provides the calling hook with the default K8s icon.
-  return \Drupal::service('cloud')->getDefaultCloudConfigIcon($entity, 'k8s');
+  return \Drupal::service(K8sHooks::class)->defaultCloudConfigIcon($entity);
 }
 
 /**
  * Implements hook_form_alter().
  */
+#[LegacyHook]
 function k8s_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  if (strpos($form_id, 'views_form_k8s_') === 0) {
-    $form['#submit'][] = 'cloud_views_bulk_form_submit';
-  }
+  \Drupal::service(K8sHooks::class)->formAlter($form, $form_state, $form_id);
 }
 
 /**
@@ -422,23 +355,9 @@ function k8s_form_alter(array &$form, FormStateInterface $form_state, $form_id):
  *
  * Combined k8s_entity_views_access and ks8s_entity_views_access_with_namespace.
  */
+#[LegacyHook]
 function k8s_query_k8s_entity_views_access_alter(AlterableInterface $query): void {
-  $route_name = \Drupal::routeMatch()->getRouteName();
-  // Regex supports the view.*.list and view.*.all pages.
-  if (!preg_match('/^view\.([a-z0-9_]+)\.(list|all)$/', $route_name, $matches)) {
-    return;
-  }
-
-  $entity_type = $matches[1];
-  $fields = \Drupal::service('entity_field.manager')->getFieldDefinitions($entity_type, $entity_type);
-
-  // Check whether there is namespace field in the entity type.
-  if (!empty($fields['namespace'])) {
-    k8s_build_namespace_query_condition($query);
-  }
-
-  // Add owner condition.
-  \Drupal::service('cloud')->buildOwnerQueryCondition($query);
+  \Drupal::service(K8sHooks::class)->queryK8sEntityViewsAccessAlter($query);
 }
 
 /**
@@ -505,226 +424,25 @@ function k8s_build_namespace_query_condition(AlterableInterface $query): void {
 /**
  * Implements hook_query_TAG_Alter().
  */
+#[LegacyHook]
 function k8s_query_all_resources_views_access_alter(AlterableInterface $query): void {
-  if (!$account = $query->getMetaData('account')) {
-    $account = \Drupal::currentUser();
-  }
-
-  $entities = \Drupal::service('plugin.manager.cloud_config_plugin')->loadConfigEntities('k8s');
-  $cloud_contexts = [];
-  foreach ($entities ?: [] as $entity) {
-    if ($account->hasPermission('view all cloud service providers') || $account->hasPermission('view ' . $entity->getCloudContext())) {
-      $cloud_contexts[] = $entity->getCloudContext();
-    }
-  }
-
-  $cloud_context_field = 'cloud_context';
-  if ($query->getMetaData('base_table')) {
-    $cloud_context_field = $query->getMetaData('base_table') . '.' . $cloud_context_field;
-  }
-  if (count($cloud_contexts)) {
-    $query->condition($cloud_context_field, $cloud_contexts, 'IN');
-  }
-  else {
-    // No permissions, do not let them view any cloud context.
-    // Return an empty page. This is just a catch-all. In
-    // normal cases, users will have access to certain cloud context.
-    $query->condition($cloud_context_field, '');
-  }
+  \Drupal::service(K8sHooks::class)->queryAllResourcesViewsAccessAlter($query);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function k8s_form_views_exposed_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-
-  // If not the view we are looking, move on.
-  if (strpos($form['#id'], 'views-exposed-form-k8s-') !== 0) {
-    return;
-  }
-
-  $route = \Drupal::routeMatch();
-  if ($route->getRouteName() === 'views.ajax') {
-    // When the request is from ajax, get the cloud context from referer.
-    global $base_url;
-    // Get the referer url.
-    $referer = \Drupal::request()->headers->get('referer');
-    if (!empty($referer)) {
-      // Get the alias or the referer.
-      $alias = substr($referer, strlen($base_url));
-      $url = Url::fromUri('internal:' . $alias);
-      $route_parameters = $url->getRouteParameters();
-      $cloud_context = $route_parameters['cloud_context'] ?? NULL;
-      $cloud_project_id = $route_parameters['cloud_project'] ?? NULL;
-    }
-  }
-  else {
-    $cloud_context = $route->getParameter('cloud_context');
-    $cloud_project_id = $route->getParameter('cloud_project');
-  }
-
-  $cloud_project = NULL;
-  $k8s_clusters = [];
-  if (!empty($cloud_project_id)) {
-    $cloud_project = \Drupal::entityTypeManager()->getStorage('cloud_project')->load($cloud_project_id);
-    if (!empty($cloud_project)) {
-      foreach ($cloud_project->get('field_k8s_clusters')->getValue() ?: [] as $k8s_cluster) {
-        $k8s_clusters[] = $k8s_cluster['value'];
-      }
-    }
-  }
-
-  if ((empty($cloud_context) || !empty($cloud_project_id)) && !empty($form['cloud_context'])) {
-    $account = \Drupal::currentUser();
-
-    $entities = \Drupal::service('plugin.manager.cloud_config_plugin')
-      ->loadConfigEntities('k8s');
-    $options = [];
-    foreach ($entities ?: [] as $entity) {
-      if ($account->hasPermission('view all cloud service providers') || $account->hasPermission('view ' . $entity->getCloudContext())) {
-        $options[$entity->getCloudContext()] = $entity->getName();
-      }
-    }
-
-    if (!empty($cloud_project) && !empty($options)) {
-      foreach ($options ?: [] as $key => $option) {
-        if (!in_array($key, $k8s_clusters)) {
-          unset($options[$key]);
-        }
-      }
-    }
-
-    $form['cloud_context'] = [
-      '#type' => 'select',
-      '#multiple' => FALSE,
-      '#empty_option' => t('- Any -'),
-      '#options' => $options,
-      '#weight' => -60,
-    ];
-  }
-
-  // Query namespaces.
-  $storage = Drupal::getContainer()
-    ->get('entity_type.manager')
-    ->getStorage('k8s_namespace');
-
-  // Gather namespaces sort by title.
-  $query = $storage
-    ->getQuery()
-    ->accessCheck(TRUE);
-
-  if (!empty($cloud_project_id)) {
-    $query = $query->condition('cloud_context', $k8s_clusters, 'IN');
-  }
-  elseif (!empty($cloud_context)) {
-    $query = $query->condition('cloud_context', $cloud_context);
-  }
-
-  $namespace_ids = $query
-    ->sort('name')
-    ->execute();
-
-  // If there are no namespaces, move on.
-  if (!$namespace_ids) {
-    return;
-  }
-
-  // Start building out the options for our select list.
-  $options = [];
-  $namespaces = $storage->loadMultiple($namespace_ids);
-
-  // Push titles into select list.
-  $account = \Drupal::currentUser();
-  foreach ($namespaces ?: [] as $namespace) {
-    if ($account->hasPermission('view any k8s namespace entities') || $account->hasPermission('view k8s namespace ' . $namespace->getName())) {
-      $options[$namespace->getName()] = $namespace->getName();
-    }
-  }
-
-  // Replace namespace item to select item.
-  if (!empty($form['namespace'])) {
-    $form['namespace'] = [
-      '#type' => 'select',
-      '#multiple' => FALSE,
-      '#empty_option' => t('- Any -'),
-      '#options' => $options,
-    ];
-
-    // For all resource view.
-    if (empty($cloud_context) || !empty($cloud_project_id)) {
-      $cloud_context_namespaces = [];
-      foreach ($namespaces ?: [] as $namespace) {
-        $cloud_context_namespaces[$namespace->getCloudContext()][$namespace->getName()] = $namespace->getName();
-      }
-      $form['namespace']['#attached']['library'][] = 'k8s/k8s_all_resources';
-      $form['namespace']['#attached']['drupalSettings']['k8s']['cloud_context_namespaces'] = $cloud_context_namespaces;
-    }
-  }
-
-  // Query pods.
-  $storage = Drupal::getContainer()
-    ->get('entity_type.manager')
-    ->getStorage('k8s_pod');
-
-  // Gather pods sort by title.
-  $query = $storage
-    ->getQuery()
-    ->accessCheck(TRUE);
-
-  if (!empty($cloud_project_id)) {
-    $query = $query->condition('cloud_context', $k8s_clusters, 'IN');
-  }
-  elseif (!empty($cloud_context)) {
-    $query = $query->condition('cloud_context', $cloud_context);
-  }
-
-  $pod_ids = $query
-    ->sort('status')
-    ->execute();
-
-  $pods = $storage->loadMultiple($pod_ids);
-
-  $options = [];
-  foreach ($pods ?: [] as $pod) {
-    $value = $pod->get('status')->value ?? NULL;
-
-    if (!empty($pod) && $value !== NULL && !array_key_exists($value, $options)) {
-      $options[$value] = $value;
-    }
-  }
-
-  // Replace pod status item to select item.
-  if (!empty($form['status'])) {
-    $form['status'] = [
-      '#type' => 'select',
-      '#multiple' => FALSE,
-      '#empty_option' => t('- Any -'),
-      '#options' => $options,
-    ];
-  }
+  \Drupal::service(K8sHooks::class)->formViewsExposedFormAlter($form, $form_state, $form_id);
 }
 
 /**
  * Implements hook_ENTITY_TYPE_delete().
  */
+#[LegacyHook]
 function k8s_k8s_namespace_delete(K8sNamespace $namespace): void {
-  \Drupal::service('cloud')->invalidateCacheTags($namespace->getCacheTags());
-
-  // Delete cloud store entities and cache.
-  $k8s_service = \Drupal::service('k8s');
-  $k8s_service->setCloudContext($namespace->getCloudContext());
-  $k8s_service->deleteK8sCloudStore(
-    0,
-    ['k8s_namespace_resource_store', 'k8s_cost_store'],
-    $namespace->getName()
-  );
-
-  $cache_service = \Drupal::service('cloud.cache');
-  $cache_service->deleteResourceDataCache(
-    $namespace->getCloudContext(),
-    'k8s_pod',
-    $namespace->getName());
-
+  \Drupal::service(K8sHooks::class)->k8sNamespaceDelete($namespace);
 }
 
 /**
@@ -864,244 +582,25 @@ function k8s_time_scheduler_option_allowed_values_function(FieldStorageConfig $d
  * @throws \Drupal\k8s\Service\K8sServiceException
  *   Thrown when unable to get metrics nodes.
  */
+#[LegacyHook]
 function k8s_entity_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display): void {
-  $k8s_service = \Drupal::service('k8s');
-  $source_type = 'yml';
-  if ($entity->getEntityTypeId() === 'cloud_launch_template'
-    && $entity->bundle() === 'k8s'
-  ) {
-    if (!empty($build['field_git_access_token'][0]['#context']['value'])) {
-      $build['field_git_access_token'][0]['#context']['value'] = '********';
-      // If the source is git,
-      // change the label.
-      if (!empty($build['field_source_type'][0]['#markup'])) {
-        $source_type = $build['field_source_type'][0]['#markup'];
-        switch ($build['field_source_type'][0]['#markup']) {
-          // If the source is GitHub, change the label.
-          case 'Git':
-            $build['field_git_username']['#title'] = t('Git username');
-            $build['field_git_access_token']['#title'] = t('Git password');
-            break;
-
-          // If the source is GitHub, change the label.
-          case 'GitHub':
-            $build['field_git_username']['#title'] = t('GitHub Username');
-            $build['field_git_access_token']['#title'] = t('GitHub Access Token');
-            break;
-
-          // If the source is GitHub, change the label.
-          case 'GitLab':
-            $build['field_git_username']['#title'] = t('GitLab Username');
-            $build['field_git_access_token']['#title'] = t('GitLab Access Token');
-            break;
-        }
-      }
-
-    }
-
-    // Configure Source fields.
-    $configuration_fields = [
-      [
-        'name'  => 'source',
-        'title' => t('Source'),
-        'open' => TRUE,
-        'fields' => [
-          'field_source_type',
-          'field_git_repository',
-        ],
-      ],
-      [
-        'name' => 'credentials',
-        'title' => t('Credentials'),
-        'open' => TRUE,
-        'fields' => [
-          'field_git_username',
-          'field_git_access_token',
-        ],
-      ],
-      [
-        'name' => 'branch',
-        'title' => t('Branch'),
-        'open' => TRUE,
-        'fields' => [
-          'field_git_branch',
-          'field_git_path',
-        ],
-      ],
-      [
-        'name' => 'details',
-        'title' => t('Details'),
-        'open' => [
-          'textarea[name="field_detail[0][value]"]' => [['!value' => '']],
-        ],
-        'fields' => [
-          'field_yaml_url',
-          'field_detail',
-        ],
-      ],
-    ];
-    \Drupal::service('cloud')->reorderForm($build, $configuration_fields);
-
-    $is_metrics_server = empty($entity)
-        || empty($entity->hasField('field_is_metrics_server'))
-        || empty($entity->get('field_is_metrics_server'))
-      ? FALSE
-      : $entity->field_is_metrics_server->value;
-    $source_type = $entity->get('field_source_type')->getValue();
-    \Drupal::service('cloud')->reorderForm(
-      $build,
-      k8s_launch_template_field_orders(
-        FALSE,
-        $is_metrics_server,
-        FALSE,
-        str_contains($source_type[0]['value'], 'git')
-      )
-    );
-    $build['k8s']['cloud_context'] = $entity->cloud_context->view();
-    $build['k8s']['cloud_context']['#title'] = t('K8s cluster');
-    $build['k8s']['cloud_context']['#label_display'] = 'inline';
-    $values = $k8s_service->clusterAllowedValues();
-
-    $build['k8s']['cloud_context'][0]['#context']['value'] = $values[$build['k8s']['cloud_context'][0]['#context']['value']];
-    $build['k8s']['field_object']['#title'] = t('Kind');
-
-    if ((!empty($build['configuration_git']['source']['field_source_type']) && !str_contains($build['configuration_git']['source']['field_source_type'][0]['#markup'], 'Git'))
-      || (empty($build['configuration_git']['credentials']['field_git_access_token'][0]['#context']['value'])
-      && empty($build['configuration_git']['credentials']['field_git_username'][0]['#context']['value']))) {
-      unset($build['configuration_git']['credentials']);
-    }
-
-    if (empty($build['configuration_yml']['details']['field_detail'][0]['#markup'])) {
-      unset($build['configuration_yml']['details']);
-    }
-
-    // Check if field_launch_template_parameters is empty,
-    // and remove fieldset if it is.
-    if (empty($build['launch_template_parameters']['field_launch_template_parameters'][0])) {
-      unset($build['launch_template_parameters']);
-    }
-
-    $git_available = $k8s_service->isGitCommandAvailable();
-    if (!$git_available && str_contains($source_type[0]['value'], 'git')) {
-      $messages = \Drupal::messenger()->messagesByType(\Drupal::messenger()::TYPE_ERROR);
-      $warning = t('A git command could not be found on the server.  Install the git command and try again.');
-      $message = Markup::create((string) $warning);
-      if (!in_array($message, $messages)) {
-        \Drupal::messenger()->addWarning($warning);
-      }
-    }
-
-    if ($build['time_scheduler']['field_enable_time_scheduler']['0']['#markup'] === 'Off') {
-      unset($build['time_scheduler']);
-    }
-    else {
-      $startup_hour = $build['time_scheduler']['field_startup_time_hour'][0]['#markup'];
-      $startup_minute = $build['time_scheduler']['field_startup_time_minute'][0]['#markup'];
-      $stop_hour = $build['time_scheduler']['field_stop_time_hour'][0]['#markup'];
-      $stop_minute = $build['time_scheduler']['field_stop_time_minute'][0]['#markup'];
-
-      $build['time_scheduler']['field_enable_time_scheduler']['#label_display'] = 'inline';
-      $build['time_scheduler']['field_startup_time_hour']['#title'] = 'Start-up time';
-      $build['time_scheduler']['field_startup_time_hour']['#label_display'] = 'inline';
-      $build['time_scheduler']['field_startup_time_hour'][0]['#markup'] = $startup_hour . ':' . $startup_minute;
-
-      $build['time_scheduler']['field_stop_time_hour']['#title'] = 'Stop time';
-      $build['time_scheduler']['field_stop_time_hour']['#label_display'] = 'inline';
-      $build['time_scheduler']['field_stop_time_hour'][0]['#markup'] = $stop_hour . ':' . $stop_minute;
-      unset($build['time_scheduler']['field_startup_time_minute']);
-      unset($build['time_scheduler']['field_stop_time_minute']);
-    }
-  }
-
-  if (in_array($entity->getEntityTypeId(), ['k8s_node', 'k8s_pod'])
-    && !empty($entity->getCloudContext())) {
-
-    // Confirm whether the metrics API can be used or not.
-    $metrics_enabled = TRUE;
-    if (!empty($entity->getCloudContext())) {
-      $k8s_service->setCloudContext($entity->getCloudContext());
-      try {
-        $k8s_service->getMetricsNodes();
-      }
-      catch (K8sServiceException $e) {
-        $metrics_enabled = FALSE;
-      }
-    }
-
-    if (!empty($build['entity_metrics'])) {
-
-      $build['entity_metrics']['k8s_entity_metrics'] = [
-        '#markup' => '<div id="k8s_entity_metrics"></div>',
-      ];
-
-      $build['#attached']['library'][] = 'k8s/k8s_entity_metrics';
-    }
-
-    $build['#attached']['drupalSettings']['k8s']['metrics_enabled'] = $metrics_enabled;
-    $config = \Drupal::config('k8s.settings');
-    $build['#attached']['drupalSettings']['k8s']['k8s_js_refresh_interval']
-      = $config->get('k8s_js_refresh_interval');
-  }
-
-  if (!empty($build['node_pods'])) {
-
-    $build['node_pods']['k8s_node_pods'] = [
-      '#type' => 'view',
-      '#name' => 'k8s_pod',
-      '#display_id' => 'block_for_node',
-    ];
-
-  }
-
+  \Drupal::service(K8sHooks::class)->entityViewAlter($build, $entity, $display);
 }
 
 /**
  * Implements hook_ENTITY_TYPE_presave().
  */
+#[LegacyHook]
 function k8s_cloud_launch_template_presave(EntityInterface $entity): void {
-  if (empty($entity)
-  || $entity->bundle() !== 'k8s') {
-    return;
-  }
-
-  // If there is no info stored in field_detail, return.
-  // Otherwise, use the K8s service to decode yaml string.
-  if (empty($entity->field_detail->value)) {
-    return;
-  }
-  $k8s_service = \Drupal::service('k8s');
-  $yamls = $k8s_service->decodeMultipleDocYaml($entity->field_detail->value);
-
-  if (count($yamls) > 1) {
-    $entity->field_object->value = 'mixed';
-    return;
-  }
-
-  $yaml = $yamls[0];
-  // Set the object to NULL unless the yaml file provides a kind.
-  // Based on the ['kind'] key, search for supported templates.
-  $entity->field_object->value = NULL;
-  if (!empty($yaml['kind'])) {
-    $templates = $k8s_service->supportedCloudLaunchTemplates();
-    $object = array_search($yaml['kind'], $templates);
-    // The field 'field_object' should only be
-    // assigned the value of $object when the user selects 'yml'.
-    $entity->field_object->value = !empty($object) ? $object : NULL;
-  }
+  \Drupal::service(K8sHooks::class)->cloudLaunchTemplatePresave($entity);
 }
 
 /**
  * Implements hook_form_BASE_FORM_ID_alter().
  */
+#[LegacyHook]
 function k8s_form_cloud_launch_template_k8s_add_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  $form['field_object']['#access'] = FALSE;
-  $form['field_launch_resources']['#access'] = FALSE;
-  k8s_form_cloud_launch_template_k8s_form_common_alter($form, $form_state, $form_id);
-
-  $index = array_search('::save', $form['actions']['submit']['#submit']);
-  array_splice($form['actions']['submit']['#submit'], $index, 0, 'k8s_form_cloud_launch_template_k8s_form_submit');
-  $form['actions']['submit']['#submit'][] = 'k8s_form_cloud_launch_template_k8s_after_save';
-
+  \Drupal::service(K8sHooks::class)->formCloudLaunchTemplateK8sAddFormAlter($form, $form_state, $form_id);
 }
 
 /**
@@ -1164,43 +663,9 @@ function k8s_form_cloud_launch_template_k8s_form_submit(array $form, FormStateIn
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function k8s_form_cloud_launch_template_k8s_edit_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  $form['field_object']['#access'] = FALSE;
-  $form['field_launch_resources']['#access'] = FALSE;
-  $form['old_yaml_url'] = [
-    '#type' => 'hidden',
-    '#value' => $form['field_yaml_url']['widget'][0]['uri']['#default_value'],
-    '#disabled' => TRUE,
-  ];
-  $form['old_detail'] = [
-    '#type' => 'hidden',
-    '#value' => $form['field_detail']['widget'][0]['value']['#default_value'],
-    '#disabled' => TRUE,
-  ];
-  k8s_form_cloud_launch_template_k8s_form_common_alter($form, $form_state, $form_id);
-  // Hide new revision checkbox.
-  $form['new_revision']['#access'] = FALSE;
-
-  $cloud_context = $form['others']['cloud_context'];
-  unset($form['others']['cloud_context']);
-
-  $k8s_service = \Drupal::service('k8s');
-  $cloud_context['#disabled'] = FALSE;
-  $cloud_context['widget'][0]['value']['#title'] = t('Cluster');
-  $cloud_context['widget'][0]['value']['#type'] = 'select';
-  $cloud_context['widget'][0]['value']['#options'] = $k8s_service->clusterAllowedValues();
-  $cloud_context['widget'][0]['value']['#ajax'] = [
-    'callback' => 'k8s_ajax_callback_get_fields',
-  ];
-  unset($cloud_context['widget'][0]['value']['#size']);
-  unset($cloud_context['widget'][0]['value']['#description']);
-  $cloud_context['#weight'] = 1;
-  $form['k8s']['cloud_context'] = $cloud_context;
-
-  $form['#entity_builders'][] = 'k8s_form_cloud_launch_template_k8s_edit_entity_builder';
-  $index = array_search('::save', $form['actions']['submit']['#submit']);
-  array_splice($form['actions']['submit']['#submit'], $index, 0, 'k8s_form_cloud_launch_template_k8s_form_submit');
-  $form['actions']['submit']['#submit'][] = 'k8s_form_cloud_launch_template_k8s_after_save';
+  \Drupal::service(K8sHooks::class)->formCloudLaunchTemplateK8sEditFormAlter($form, $form_state, $form_id);
 }
 
 /**
@@ -1246,44 +711,9 @@ function k8s_form_cloud_launch_template_k8s_edit_entity_builder($entity_type_id,
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function k8s_form_cloud_launch_template_k8s_copy_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  $form['field_object']['#access'] = FALSE;
-  $form['field_launch_resources']['#access'] = FALSE;
-  $form['old_yaml_url'] = [
-    '#type' => 'hidden',
-    '#value' => $form['field_yaml_url']['widget'][0]['uri']['#default_value'],
-    '#disabled' => TRUE,
-  ];
-  $form['old_detail'] = [
-    '#type' => 'hidden',
-    '#value' => $form['field_detail']['widget'][0]['value']['#default_value'],
-    '#disabled' => TRUE,
-  ];
-
-  k8s_form_cloud_launch_template_k8s_form_common_alter($form, $form_state, $form_id);
-
-  $name = $form['k8s']['name']['widget'][0]['value']['#default_value'];
-  $form['k8s']['name']['widget'][0]['value']['#default_value'] = t('copy_of_@name',
-    [
-      '@name' => $name,
-    ]);
-
-  // Hide new revision checkbox.
-  $form['new_revision']['#access'] = FALSE;
-
-  // Clear the revision log message.
-  $form['others']['revision_log_message']['widget'][0]['value']['#default_value'] = NULL;
-
-  // Change value of the submit button.
-  $form['actions']['submit']['#value'] = t('Copy');
-
-  // Delete the delete button.
-  $form['actions']['delete']['#access'] = FALSE;
-
-  $form['#entity_builders'][] = 'k8s_form_cloud_launch_template_k8s_edit_entity_builder';
-  $form['actions']['submit']['#submit'][1] = 'k8s_form_cloud_launch_template_k8s_form_submit';
-  $form['actions']['submit']['#submit'][2] = 'k8s_form_cloud_launch_template_k8s_copy_form_submit';
-
+  \Drupal::service(K8sHooks::class)->formCloudLaunchTemplateK8sCopyFormAlter($form, $form_state, $form_id);
 }
 
 /**
@@ -1834,98 +1264,25 @@ function k8s_launch_template_field_orders(
 /**
  * Implements hook_entity_bundle_field_info_alter().
  */
+#[LegacyHook]
 function k8s_entity_bundle_field_info_alter(array &$fields, EntityTypeInterface $entity_type, $bundle): void {
-  if ($bundle === 'k8s') {
-    if (!empty($fields['field_detail'])) {
-      // Use the ID as defined in the annotation of the constraint definition.
-      $fields['field_detail']->addConstraint('yaml_array_data', []);
-      $fields['field_detail']->addConstraint('yaml_object_support', []);
-    }
-    if (!empty($fields['field_git_path'])) {
-      $fields['field_git_path']->addPropertyConstraints('value', [
-        'Regex' => [
-          'pattern' => '/^\//i',
-        ],
-      ]);
-    }
-  }
+  \Drupal::service(K8sHooks::class)->entityBundleFieldInfoAlter($fields, $entity_type, $bundle);
 }
 
 /**
  * Implements hook_cron().
  */
+#[LegacyHook]
 function k8s_cron(): void {
-  $k8s_service = \Drupal::service('k8s');
-
-  // Update resources.
-  $config_entities = \Drupal::service('plugin.manager.cloud_config_plugin')
-    ->loadConfigEntities('k8s');
-  foreach ($config_entities ?: [] as $config_entity) {
-    if ($config_entity->isRemote()) {
-      continue;
-    }
-    $k8s_service->setCloudContext($config_entity->getCloudContext());
-    $k8s_service->createResourceQueueItems();
-  }
-
-  // Update resources according to schedule entities.
-  foreach ($config_entities ?: [] as $config_entity) {
-    if ($config_entity->isRemote()) {
-      continue;
-    }
-
-    $k8s_service->setCloudContext($config_entity->getCloudContext());
-    $k8s_service->updateNodesWithoutBatch();
-    $k8s_service->updatePodsWithoutBatch();
-    $k8s_service->updateScheduledResources();
-  }
-
-  $plugin_manager = \Drupal::service('plugin.manager.cloud_store_plugin');
-  $plugin = NULL;
-  try {
-    $plugin = $plugin_manager->loadPluginVariant('k8s_cost_store');
-  }
-  catch (PluginNotFoundException $e) {
-    \Drupal::service('cloud')->handleException($e);
-
-    return;
-  }
-
-  // Delete outdated resource stores.
-  $plugin->deleteK8sResourceStoreEntity();
-
-  // Update resource stores.
-  $plugin->updateK8sResourceStoreEntity();
-
-  // Update cost stores.
-  $plugin->updateCostStoreEntity();
-
+  \Drupal::service(K8sHooks::class)->cron();
 }
 
 /**
  * Implements hook_ENTITY_TYPE_predelete().
  */
+#[LegacyHook]
 function k8s_user_predelete($account): void {
-  /** @var Drupal\cloud\Service\CloudService $cloud_service */
-  $cloud_service = \Drupal::service('cloud');
-  $entity_types = $cloud_service->getProviderEntityTypes('k8s');
-  foreach ($entity_types ?: [] as $entity_type) {
-    if (empty($entity_type)) {
-      continue;
-    }
-    $ids = \Drupal::entityTypeManager()
-      ->getStorage($entity_type->id())
-      ->getQuery()
-      ->accessCheck(TRUE)
-      ->condition('uid', $account->id())
-      ->execute();
-    if (count($ids) === 0) {
-      continue;
-    }
-    $cloud_service->reassignUids($ids, [
-      'uid' => 0,
-    ], $entity_type->id(), FALSE, $account, [], 'k8s_reassign_entity_uid_callback');
-  }
+  \Drupal::service(K8sHooks::class)->userPredelete($account);
 }
 
 /**
@@ -2180,35 +1537,9 @@ function k8s_cloud_config_fieldsets(array &$fields): void {
 /**
  * Implements hook_ENTITY_TYPE_view_alter().
  */
+#[LegacyHook]
 function k8s_cloud_config_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display): void {
-  if ($entity->bundle() === 'k8s') {
-    $map_json_url = \Drupal::config('cloud.settings')->get('cloud_custom_location_map_json_url');
-    $url = Url::fromRoute('entity.cloud_config.location', ['cloud_config' => $entity->id()])
-      ->toString();
-
-    $build['cloud_config_location_map'] = [
-      '#markup' => '<div id="cloud_config_location"></div>',
-      '#attached' => [
-        'library' => [
-          'cloud/cloud_config_location',
-        ],
-        'drupalSettings' => [
-          'cloud' => [
-            'cloud_location_map_json_url' => $map_json_url,
-            'cloud_config_location_json_url' => $url,
-          ],
-        ],
-      ],
-    ];
-
-    $build['field_location_country']['#access'] = FALSE;
-    $build['field_location_city']['#access'] = FALSE;
-    $build['field_location_longitude']['#access'] = FALSE;
-    $build['field_location_latitude']['#access'] = FALSE;
-
-    k8s_cloud_config_fieldsets($build);
-
-  }
+  \Drupal::service(K8sHooks::class)->cloudConfigViewAlter($build, $entity, $display);
 }
 
 /**
@@ -2298,38 +1629,25 @@ function k8s_entity_type_alter(array &$entity_types): void {
 /**
  * Implements hook_ENTITY_TYPE_create().
  */
+#[LegacyHook]
 function k8s_k8s_pod_create(EntityInterface $entity): void {
-  k8s_update_node_for_pod($entity);
+  \Drupal::service(K8sHooks::class)->k8sPodCreate($entity);
 }
 
 /**
  * Implements hook_ENTITY_TYPE_update().
  */
+#[LegacyHook]
 function k8s_k8s_pod_update(EntityInterface $entity): void {
-  k8s_update_node_for_pod($entity);
+  \Drupal::service(K8sHooks::class)->k8sPodUpdate($entity);
 }
 
 /**
  * Implements hook_ENTITY_TYPE_delete().
  */
+#[LegacyHook]
 function k8s_k8s_pod_delete(EntityInterface $entity): void {
-  k8s_update_node_for_pod($entity);
-
-  // Delete cloud store entities and cache.
-  $k8s_service = \Drupal::service('k8s');
-  $k8s_service->setCloudContext($entity->getCloudContext());
-  $k8s_service->deleteK8sCloudStore(
-    0,
-    ['k8s_pod_resource_store'],
-    "{$entity->getNamespace()}:{$entity->getName()}"
-  );
-
-  $cache_service = \Drupal::service('cloud.cache');
-  $cache_service->deleteResourceDataCache(
-    $entity->getCloudContext(),
-    $entity->bundle(),
-    $entity->getNamespace(),
-    $entity->getName());
+  \Drupal::service(K8sHooks::class)->k8sPodDelete($entity);
 }
 
 /**
@@ -2443,59 +1761,17 @@ function k8s_get_resource_url(): string {
 /**
  * Implements hook_preprocess_field().
  */
+#[LegacyHook]
 function k8s_preprocess_field(&$variables): void {
-  if ($variables['element']['#object']->getEntityTypeId() === 'cloud_launch_template') {
-    $name = $variables['element']['#field_name'];
-    if ($name === 'field_object' && count($variables['items']) > 1) {
-      $objects = [];
-      foreach ($variables['items'] ?: [] as $idx => &$item) {
-        $object = $item['content']['#markup'];
-        if (!empty($objects[$object])) {
-          ++$objects[$object];
-          unset($variables['items'][$idx]);
-        }
-        else {
-          $objects[$object] = 1;
-        }
-      }
-      foreach ($variables['items'] ?: [] as $idx => &$item) {
-        $object = $item['content']['#markup'];
-        $count = $objects[$object];
-        if ($count > 1) {
-          $item['content']['#markup'] = "$count $object" . 's';
-        }
-      }
-    }
-  }
+  \Drupal::service(K8sHooks::class)->preprocessField($variables);
 }
 
 /**
  * Implements hook_form_BASE_FORM_ID_alter().
  */
+#[LegacyHook]
 function k8s_form_cloud_launch_template_k8s_delete_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  $server_template = $form_state
-    ->getFormObject()
-    ->getEntity();
-
-  $resources = $server_template->get('field_launch_resources')->getValue();
-  if (!empty($resources)) {
-    $form['field_delete_option'] = [
-      '#type' => 'radios',
-      '#options' => [
-        'both' => t('Delete both application and resources'),
-        'resources' => t('Delete only resources'),
-        'application' => t('Delete only application'),
-      ],
-      '#title' => t('Delete Options'),
-      '#default_value' => 'both',
-    ];
-
-    \Drupal::messenger()->addWarning(t("Make sure the following resources will be deleted if you select <em>'Delete both application and resources'</em> or <em>'Delete only resources'</em> option."));
-    k8s_create_resources_message($form, $resources);
-
-    $index = array_search('::submitForm', $form['actions']['submit']['#submit']);
-    array_splice($form['actions']['submit']['#submit'], $index, 1, 'k8s_form_cloud_launch_template_k8s_delete_form_submit');
-  }
+  \Drupal::service(K8sHooks::class)->formCloudLaunchTemplateK8sDeleteFormAlter($form, $form_state, $form_id);
 }
 
 /**
@@ -2613,28 +1889,25 @@ function k8s_form_cloud_launch_template_k8s_delete_form_submit(array &$form, For
 /**
  * Implements hook_form_BASE_FORM_ID_alter().
  */
+#[LegacyHook]
 function k8s_form_cloud_launch_template_k8s_launch_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  if (empty($form['actions']['submit']['#submit'])) {
-    return;
-  }
-  $index = array_search('::submitForm', $form['actions']['submit']['#submit']);
-  array_splice($form['actions']['submit']['#submit'], $index, 1, 'k8s_form_cloud_launch_template_k8s_launch_form_submit');
+  \Drupal::service(K8sHooks::class)->formCloudLaunchTemplateK8sLaunchFormAlter($form, $form_state, $form_id);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function k8s_form_cloud_launch_template_k8s_review_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  $index = array_search('::save', $form['actions']['submit']['#submit']);
-  array_splice($form['actions']['submit']['#submit'], $index, 0, 'k8s_form_cloud_launch_template_k8s_review_form_submit');
+  \Drupal::service(K8sHooks::class)->formCloudLaunchTemplateK8sReviewFormAlter($form, $form_state, $form_id);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function k8s_form_cloud_launch_template_k8s_approve_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  $index = array_search('::save', $form['actions']['submit']['#submit']);
-  array_splice($form['actions']['submit']['#submit'], $index, 0, 'k8s_form_cloud_launch_template_k8s_approve_form_submit');
+  \Drupal::service(K8sHooks::class)->formCloudLaunchTemplateK8sApproveFormAlter($form, $form_state, $form_id);
 }
 
 /**
@@ -2770,31 +2043,9 @@ function k8s_create_resources_message(array &$form, array $resources): void {
 /**
  * Implements hook_entity_delete().
  */
+#[LegacyHook]
 function k8s_entity_delete(EntityInterface $entity): void {
-  if ($entity instanceof K8sEntityInterface && !empty($entity->getAnnotations())) {
-    $annotations = $entity->getAnnotations();
-    if (!empty($annotations[K8sEntityInterface::ANNOTATION_LAUNCHED_APPLICATION_ID])) {
-      $server_template_id = $annotations[K8sEntityInterface::ANNOTATION_LAUNCHED_APPLICATION_ID];
-      $server_template = \Drupal::entityTypeManager()
-        ->getStorage('cloud_launch_template')
-        ->load($server_template_id);
-      if (!empty($server_template && $server_template->hasField('field_launch_resources'))) {
-        $resources = $server_template->get('field_launch_resources')->getValue();
-        if (!empty($resources)) {
-          foreach ($resources ?: [] as $idx => $resource) {
-            $type = $resource['item_key'];
-            $id = $resource['item_value'];
-            if ($type === $entity->bundle() && $id === $entity->id()) {
-              $server_template->get('field_launch_resources')->removeItem($idx);
-              $server_template->setValidationRequired(FALSE);
-              $server_template->save();
-              break;
-            }
-          }
-        }
-      }
-    }
-  }
+  \Drupal::service(K8sHooks::class)->entityDelete($entity);
 }
 
 /**
@@ -2855,85 +2106,9 @@ function k8s_get_user_list(): array {
 /**
  * Implements hook_ENTITY_TYPE_delete().
  */
+#[LegacyHook]
 function k8s_cloud_project_delete(EntityInterface $entity): void {
-
-  if ($entity->bundle() !== 'k8s' || $entity->getEntityType()->id() !== 'cloud_project') {
-    return;
-  }
-
-  $k8s_clusters = $entity->get('field_k8s_clusters');
-  $messenger = \Drupal::messenger();
-  $k8s_cluster_list = [];
-
-  foreach ($k8s_clusters ?: [] as $cloud_context) {
-    if (!empty($cloud_context->value)) {
-      $k8s_cluster_list[$cloud_context->value] = $cloud_context->value;
-    }
-  }
-
-  $k8s_resource_list = [
-    'k8s_namespace',
-    'k8s_resource_quota',
-  ];
-
-  try {
-
-    k8s_delete_specific_resource($k8s_cluster_list, $k8s_resource_list, $entity->getName());
-
-    // Delete the role if it exists.
-    $roles = \Drupal::entityTypeManager()->getStorage('user_role')
-      ->loadByProperties([
-        'id' => $entity->getName(),
-      ]);
-
-    if (!empty($roles)) {
-
-      $role = array_shift($roles);
-      $role->delete();
-
-      \Drupal::service('cloud')->processOperationStatus($role, 'deleted');
-    }
-
-    $message_all = $messenger->all();
-    $messages = array_shift($message_all);
-    $messenger->deleteAll();
-
-    $output = '';
-    $search = "/ {$entity->getName()} /";
-
-    foreach ($messages ?: [] as $message) {
-
-      $string = $message->jsonSerialize();
-      if (preg_match($search, $string)) {
-        $output .= "<li>{$message}</li>";
-      }
-      else {
-        $messenger->addStatus($message);
-      }
-    }
-
-    $messenger->addStatus(t('The @type @label has been deleted.<ul>@output</ul>', [
-      '@type' => $entity->getEntityType()->getSingularLabel(),
-      '@label' => $entity->label(),
-      '@output' => Markup::Create($output),
-    ]));
-
-    \Drupal::logger('k8s')->info(t('@type: deleted %label.', [
-      '@type' => $entity->getEntityType()->getSingularLabel(),
-      '%label' => $entity->label(),
-    ]));
-
-  }
-  catch (EntityStorageException $e) {
-
-    try {
-
-      \Drupal::service('cloud')->processOperationErrorStatus($entity, 'deleted');
-    }
-    catch (EntityMalformedException $e) {
-      \Drupal::service('cloud')->handleException($e);
-    }
-  }
+  \Drupal::service(K8sHooks::class)->cloudProjectDelete($entity);
 }
 
 /**
@@ -2977,37 +2152,25 @@ function k8s_delete_specific_resource(array $k8s_cluster_list, array $k8s_resour
 /**
  * Implements hook_form_BASE_FORM_ID_alter().
  */
+#[LegacyHook]
 function k8s_form_cloud_project_k8s_add_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  k8s_form_cloud_project_k8s_form_common_alter($form, $form_state, $form_id);
+  \Drupal::service(K8sHooks::class)->formCloudProjectK8sAddFormAlter($form, $form_state, $form_id);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function k8s_form_cloud_project_k8s_edit_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  k8s_form_cloud_project_k8s_form_common_alter($form, $form_state, $form_id);
-  // Hide new revision checkbox.
-  $form['new_revision']['#access'] = FALSE;
-  $form['project']['name']['#access'] = FALSE;
+  \Drupal::service(K8sHooks::class)->formCloudProjectK8sEditFormAlter($form, $form_state, $form_id);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function k8s_form_cloud_project_k8s_copy_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  k8s_form_cloud_project_k8s_form_common_alter($form, $form_state, $form_id);
-
-  // Clear the revision log message.
-  $form['others']['revision_log_message']['widget'][0]['value']['#default_value'] = NULL;
-
-  // Change value of the submit button.
-  $form['actions']['submit']['#value'] = t('Copy');
-
-  // Delete the delete button.
-  $form['actions']['delete']['#access'] = FALSE;
-
-  $form['actions']['submit']['#submit'][1] = 'k8s_form_cloud_project_k8s_copy_form_submit';
-
+  \Drupal::service(K8sHooks::class)->formCloudProjectK8sCopyFormAlter($form, $form_state, $form_id);
 }
 
 /**
@@ -3178,80 +2341,9 @@ function k8s_cloud_project_view_alter(array &$build, EntityInterface $entity, En
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function k8s_form_cloud_project_k8s_launch_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  $entity = $form_state->getFormObject()->getEntity();
-  $fieldset_defs = k8s_project_field_orders();
-  $entity_type = $entity->getEntityTypeId();
-  $bundle = $entity->bundle();
-  $field_definitions = \Drupal::service('entity_field.manager')->getFieldDefinitions($entity_type, $bundle);
-
-  $weight = 4;
-  foreach ($fieldset_defs ?: [] as $fieldset_def) {
-    $fieldset_name = $fieldset_def['name'];
-    $form[$fieldset_name] = [
-      '#type' => 'details',
-      '#title' => $fieldset_def['title'],
-      '#weight' => $weight++,
-      '#open' => $fieldset_def['open'],
-    ];
-
-    foreach ($fieldset_def['fields'] ?: [] as $field_name) {
-      if (!$entity->hasField($field_name) || empty($field_name)) {
-        continue;
-      }
-
-      $label = FieldConfig::loadByName($entity_type, $bundle, $field_name);
-      $values = array_column($entity->get($field_name)->getValue(), 'value');
-      if (!empty($label)) {
-        $form[$fieldset_name][$field_name]['widget'][0] = [
-          '#label_display' => 'inline',
-          '#type' => 'item',
-          '#title' => $label->getLabel() . ':',
-          '#markup' => implode(", ", $values),
-        ];
-      }
-      else {
-        $form[$fieldset_name][$field_name]['widget'][0] = [
-          '#label_display' => 'inline',
-          '#type' => 'item',
-          '#title' => $field_definitions[$field_name]->getLabel() . ':',
-          '#markup' => implode(", ", $values),
-        ];
-      }
-      $form[$fieldset_name][$field_name]['#weight'] = $weight++;
-    }
-  }
-
-  $user = User::load(\Drupal::currentUser()->id());
-  $form['username'] = [
-    '#type' => 'details',
-    '#title' => t('Username'),
-    '#weight' => $weight++,
-    '#open' => TRUE,
-  ];
-  $form['username']['launch_user'] = [
-    '#type'          => 'entity_autocomplete',
-    '#target_type'   => 'user',
-    '#title'         => t('Launch as'),
-    '#size'          => 60,
-    '#default_value' => $user,
-    '#weight'        => 0,
-    '#required'      => TRUE,
-    '#selection_handler' => 'default:launch_project_user',
-    '#element_validate' => [
-      [EntityAutocomplete::class, 'validateEntityAutocomplete'],
-      [LaunchProjectUserSelection::class, 'validateUser'],
-    ],
-  ];
-
-  unset($form['others']);
-
-  if ($form['resource_scheduler']['field_enable_resource_scheduler']['widget'][0]['#markup'] === '0') {
-    unset($form['resource_scheduler']);
-  }
-  else {
-    $form['resource_scheduler']['field_enable_resource_scheduler']['widget'][0]['#markup'] = 'On';
-  }
+  \Drupal::service(K8sHooks::class)->formCloudProjectK8sLaunchFormAlter($form, $form_state, $form_id);
 }
 
 /**
@@ -3386,41 +2478,9 @@ function k8s_form_cloud_launch_template_k8s_after_save(array $form, FormStateInt
 /**
  * Implements hook_form_BASE_FORM_ID_alter().
  */
+#[LegacyHook]
 function k8s_form_cloud_store_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  // Get operation.
-  $operation = $form_state->getBuildInfo()['callback_object']->getOperation();
-
-  // To check whether cloud_context exists or not.
-  if (!empty($form['store']['cloud_context'])) {
-    $k8s_service = \Drupal::service('k8s');
-    $cloud_context = $form['store']['cloud_context'];
-
-    $cloud_context['widget'][0]['value']['#type'] = 'select';
-    $cloud_context['widget'][0]['value']['#options'] = $k8s_service->clusterAllowedValues();
-    $cloud_context['widget'][0]['value']['#ajax'] = [
-      'callback' => 'k8s_ajax_callback_get_fields',
-    ];
-    unset($cloud_context['widget'][0]['value']['#size']);
-    $cloud_context['#weight'] = 1;
-    $form['store']['cloud_context'] = $cloud_context;
-  }
-
-  // To change name for copy_form.
-  switch ($operation) {
-    case 'edit':
-    case 'add':
-      break;
-
-    case 'copy':
-      // Change value of the submit button.
-      $form['actions']['submit']['#value'] = t('Copy');
-
-      // Delete the delete button.
-      $form['actions']['delete']['#access'] = FALSE;
-
-      $form['actions']['submit']['#submit'][1] = 'k8s_form_cloud_store_k8s_copy_form_submit';
-      break;
-  }
+  \Drupal::service(K8sHooks::class)->formCloudStoreFormAlter($form, $form_state, $form_id);
 }
 
 /**
@@ -3743,53 +2803,15 @@ function k8s_get_access_token_url(array &$form, FormStateInterface $form_state):
 /**
  * Implements hook_ENTITY_TYPE_delete().
  */
+#[LegacyHook]
 function k8s_k8s_node_delete(K8sNode $node): void {
-  // Delete cloud store entities and cache.
-  $k8s_service = \Drupal::service('k8s');
-  $k8s_service->setCloudContext($node->getCloudContext());
-  $k8s_service->deleteK8sCloudStore(
-    0,
-    ['k8s_node_resource_store'],
-    $node->getName()
-  );
-
-  $cache_service = \Drupal::service('cloud.cache');
-  $cache_service->deleteResourceDataCache(
-    $node->getCloudContext(),
-    $node->bundle(),
-    $node->getName());
+  \Drupal::service(K8sHooks::class)->k8sNodeDelete($node);
 }
 
 /**
  * Implements hook_rebuild().
  */
+#[LegacyHook]
 function k8s_rebuild(): void {
-  // Rebuild the latest created time cache.
-  $cache_service = \Drupal::service('cloud.cache');
-
-  $max_alias = 'max_created';
-  $results = \Drupal::entityTypeManager()->getStorage('cloud_store')->getAggregateQuery()
-    ->accessCheck(TRUE)
-    ->aggregate('created', 'MAX', NULL, $max_alias)
-    ->condition('type', [
-      'k8s_namespace_resource_store',
-      'k8s_node_resource_store',
-      'k8s_pod_resource_store',
-    ], 'IN')
-    ->groupBy('type')
-    ->groupBy('cloud_context')
-    ->groupBy('name')
-    ->execute();
-
-  if (empty($results)) {
-    return;
-  }
-
-  foreach ($results ?: [] as $data) {
-    $cache_service->setLatestCreatedTimeCache(
-      $data['cloud_context'],
-      $data['type'],
-      $data['name'],
-      $data[$max_alias]);
-  }
+  \Drupal::service(K8sHooks::class)->rebuild();
 }
diff --git a/modules/cloud_service_providers/k8s/k8s.services.yml b/modules/cloud_service_providers/k8s/k8s.services.yml
index 870c452..abd4ed7 100644
--- a/modules/cloud_service_providers/k8s/k8s.services.yml
+++ b/modules/cloud_service_providers/k8s/k8s.services.yml
@@ -25,3 +25,11 @@ services:
     parent: default_plugin_manager
     tags:
       - { name: plugin_manager_cache_clear }
+
+  Drupal\k8s\Hook\K8sHooks:
+    class: Drupal\k8s\Hook\K8sHooks
+    autowire: true
+
+  Drupal\k8s\Hook\K8sTokensHooks:
+    class: Drupal\k8s\Hook\K8sTokensHooks
+    autowire: true
diff --git a/modules/cloud_service_providers/k8s/k8s.tokens.inc b/modules/cloud_service_providers/k8s/k8s.tokens.inc
index 272487c..6c6c808 100644
--- a/modules/cloud_service_providers/k8s/k8s.tokens.inc
+++ b/modules/cloud_service_providers/k8s/k8s.tokens.inc
@@ -5,80 +5,24 @@
  * Builds placeholder replacement tokens for k8s-related data.
  */
 
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\k8s\Hook\K8sTokensHooks;
 use Drupal\Core\Render\BubbleableMetadata;
 
 /**
  * Implements hook_token_info().
  */
+#[LegacyHook]
 function k8s_token_info(): array {
-  $types['k8s_launch_template'] = [
-    'name' => t('K8s Launch Template'),
-    'description' => t('Tokens related to individual K8s launch template.'),
-    'needs-data' => 'k8s_launch_template',
-  ];
-  $launch_template['name'] = [
-    'name' => t('List of launch template name'),
-    'description' => t('Enter the name of the launch template entity.'),
-  ];
-  $launch_template['launch_template_link'] = [
-    'name' => t('Launch Template Link'),
-    'description' => t('An absolute link to launch template.'),
-  ];
-  $launch_template['launch_template_edit_link'] = [
-    'name' => t('Launch Template Edit Link'),
-    'description' => t('An absolute link to edit the launch template.'),
-  ];
-  $launch_template['changed'] = [
-    'name' => t('Change date'),
-    'description' => t('The launch template change date.'),
-  ];
-  $types['k8s_launch_template_email'] = [
-    'name' => t('K8s Launch Template Email'),
-    'description' => t('Tokens related to individual K8s launch template email.'),
-    'needs-data' => 'k8s_launch_template_email',
-  ];
-  $launch_template_email['launch_templates'] = [
-    'name' => t('List of launch templates'),
-    'description' => t('List of launch templates to display to a user.'),
-  ];
-  $types['k8s_launch_template_request_email'] = [
-    'name' => t('K8s Launch Template Request Email'),
-    'description' => t('Tokens related to individual K8s launch template request email.'),
-    'needs-data' => 'k8s_launch_template_request_email',
-  ];
-  $launch_template_request_email['launch_templates_request'] = [
-    'name' => t('List of request launch templates'),
-    'description' => t('List of request launch templates to display to a user.'),
-  ];
-  return [
-    'types' => $types,
-    'tokens' => [
-      'k8s_launch_template' => $launch_template,
-      'k8s_launch_template_email' => $launch_template_email,
-      'k8s_launch_template_request_email' => $launch_template_request_email,
-    ],
-  ];
+  return \Drupal::service(K8sTokensHooks::class)->tokenInfo();
 }
 
 /**
  * Implements hook_tokens().
  */
+#[LegacyHook]
 function k8s_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata): array {
-
-  $replacements = [];
-  if ($type === 'k8s_launch_template' && !empty($data['k8s_launch_template'])) {
-    $replacements = k8s_launch_template_tokens($tokens, $data);
-  }
-  elseif ($type === 'k8s_launch_template_request' && !empty($data['k8s_launch_template_request'])) {
-    $replacements = k8s_launch_template_request_tokens($tokens, $data);
-  }
-  elseif ($type === 'k8s_launch_template_email') {
-    $replacements = k8s_launch_template_email_tokens($tokens, $data);
-  }
-  elseif ($type === 'k8s_launch_template_request_email') {
-    $replacements = k8s_launch_template_request_email_tokens($tokens, $data);
-  }
-  return $replacements;
+  return \Drupal::service(K8sTokensHooks::class)->tokens($type, $tokens, $data, $options, $bubbleable_metadata);
 }
 
 /**
diff --git a/modules/cloud_service_providers/k8s/src/Form/Config/K8sAdminNotifications.php b/modules/cloud_service_providers/k8s/src/Form/Config/K8sAdminNotifications.php
index 9d3fa03..55722e7 100755
--- a/modules/cloud_service_providers/k8s/src/Form/Config/K8sAdminNotifications.php
+++ b/modules/cloud_service_providers/k8s/src/Form/Config/K8sAdminNotifications.php
@@ -212,7 +212,7 @@ class K8sAdminNotifications extends ConfigFormBase {
 
     $views_settings = [];
     $views_settings['k8s_view_items_per_page'] = (int) $form_state->getValue('k8s_view_items_per_page');
-    $views_settings['k8s_view_expose_items_per_page'] = (boolean) $form_state->getValue('k8s_view_expose_items_per_page');
+    $views_settings['k8s_view_expose_items_per_page'] = (bool) $form_state->getValue('k8s_view_expose_items_per_page');
     $this->updateViewsPagerOptions('k8s', $views_settings);
 
     parent::submitForm($form, $form_state);
diff --git a/modules/cloud_service_providers/k8s/src/Form/Config/K8sAdminSettings.php b/modules/cloud_service_providers/k8s/src/Form/Config/K8sAdminSettings.php
index 99a800b..aca305e 100755
--- a/modules/cloud_service_providers/k8s/src/Form/Config/K8sAdminSettings.php
+++ b/modules/cloud_service_providers/k8s/src/Form/Config/K8sAdminSettings.php
@@ -257,7 +257,7 @@ class K8sAdminSettings extends ConfigFormBase {
 
     $views_settings = [];
     $views_settings['k8s_view_items_per_page'] = (int) $form_state->getValue('k8s_view_items_per_page');
-    $views_settings['k8s_view_expose_items_per_page'] = (boolean) $form_state->getValue('k8s_view_expose_items_per_page');
+    $views_settings['k8s_view_expose_items_per_page'] = (bool) $form_state->getValue('k8s_view_expose_items_per_page');
     $this->updateViewsPagerOptions('k8s', $views_settings);
 
     parent::submitForm($form, $form_state);
diff --git a/modules/cloud_service_providers/k8s/src/Hook/K8sHooks.php b/modules/cloud_service_providers/k8s/src/Hook/K8sHooks.php
new file mode 100644
index 0000000..a52d23e
--- /dev/null
+++ b/modules/cloud_service_providers/k8s/src/Hook/K8sHooks.php
@@ -0,0 +1,1170 @@
+<?php
+
+namespace Drupal\k8s\Hook;
+
+use Drupal\k8s\Entity\K8sNode;
+use Drupal\k8s\Plugin\EntityReferenceSelection\LaunchProjectUserSelection;
+use Drupal\Core\Entity\Element\EntityAutocomplete;
+use Drupal\user\Entity\User;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\Core\Entity\EntityMalformedException;
+use Drupal\Core\Entity\EntityStorageException;
+use Drupal\k8s\Entity\K8sEntityInterface;
+use Drupal\Component\Plugin\Exception\PluginNotFoundException;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\k8s\Service\K8sServiceException;
+use Drupal\Core\Render\Markup;
+use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
+use Drupal\k8s\Entity\K8sNamespace;
+use Drupal\Core\Url;
+use Drupal\Core\Database\Query\AlterableInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\cloud\Entity\CloudConfig;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for k8s.
+ */
+class K8sHooks {
+  use StringTranslationTrait;
+
+  /**
+   * Implements hook_help().
+   */
+  #[Hook('help')]
+  public function help($route_name, RouteMatchInterface $route_match): string {
+    switch ($route_name) {
+      case 'help.page.k8s':
+        $output = '<h3>' . $this->t('About') . '</h3>';
+        $output .= '<ul>';
+        $output .= '<li>' . \Drupal::service('cloud')->getVersion() . '</li>';
+        $output .= '<li>' . $this->t('This module creates a user interface for managing Kubernetes (K8s).') . '</li>';
+        $output .= '</ul>';
+        $output .= '<h3>' . $this->t('Features') . '</h3>';
+        $output .= '<ul>';
+        $output .= '<li>' . $this->t('<strong>K8s</strong>');
+        $output .= '<ul>';
+        $output .= '<li>' . $this->t('Manage K8s clusters.') . '</li>';
+        $output .= '<li>' . $this->t('Manage most of K8s resources.') . '</li>';
+        $output .= '<li>' . $this->t('Support multi-tenant like a K8s as a service.') . '</li>';
+        $output .= '<li>' . $this->t('Support K8s clusters under fully closed (Internet unreachable) network.') . '</li>';
+        $output .= '</ul></li></ul>';
+        $output .= '<ul>';
+        $output .= '<li>' . $this->t('<strong>K8s resources optimization</strong>');
+        $output .= '<ul>';
+        $output .= '<li>' . $this->t('Integrate w/ Amazon EC2 / EKS.') . '</li>';
+        $output .= '<li>' . $this->t('Visualize costs on EKS.') . '</li>';
+        $output .= '<li>' . $this->t('Schedule and manage K8s resource allocation based on entire resource utilization.') . '</li>';
+        $output .= '<li>' . $this->t('Support resource deployment under multi-cluster environment.') . '</li>';
+        $output .= '</ul></li></ul>';
+        $output .= '<p>' . $this->t('For more information, see the <a href=":cloud_documentation">online documentation for the K8s module</a>.', [
+          ':cloud_documentation' => 'https://www.drupal.org/docs/8/modules/cloud',
+        ]) . '</p>';
+        break;
+
+      default:
+        $output = '';
+    }
+    return $output;
+  }
+
+  /**
+   * Implements hook_cloud_config_update().
+   */
+  #[Hook('cloud_config_update')]
+  public static function cloudConfigUpdate(CloudConfig $cloud_config): void {
+    // Only perform resource update if coming from the cloud service provider
+    // edit form.  This hook can be triggered during user_delete().  user_delete()
+    // runs in progressive batch mode and `k8s_cloud_config_update()` will
+    // try to run a second non-progressive batch mode.  This confuses Drupal
+    // and the original batch processing will never complete, causing the
+    // site to hang.
+    if ($cloud_config->bundle() === 'k8s' && \Drupal::routeMatch()->getRouteName() === 'entity.cloud_config.edit_form') {
+      // Update resources.
+      k8s_update_resources($cloud_config->getCloudContext());
+    }
+  }
+
+  /**
+   * Implements hook_cloud_config_predelete().
+   */
+  #[Hook('cloud_config_predelete')]
+  public static function cloudConfigPredelete(CloudConfig $cloud_config): void {
+    // NOTE: Using hook_entity_predelete() to make sure the K8s CloudConfigPlugin
+    // is still available to clear out the K8s entities.
+    // This is to fix an issue using bulk operation to delete AWS and K8s
+    // cloud service providers.
+    // See https://www.drupal.org/project/cloud/issues/3186140
+    if ($cloud_config->bundle() === 'k8s') {
+      $cloud_context = $cloud_config->getCloudContext();
+      /** @var \Drupal\k8s\Service\K8sServiceInterface $k8s_service */
+      $k8s_service = \Drupal::service('k8s');
+      $k8s_service->setCloudContext($cloud_context);
+      \Drupal::service('cloud')->clearAllEntities('k8s', $cloud_context);
+      \Drupal::service('cloud')->deleteQueue(\Drupal::service('cloud')->getResourceQueueName($cloud_config));
+    }
+    // Clear resource data cache.
+    $cache_service = \Drupal::service('cloud.cache');
+    $cache_service->clearResourceDataCache($cloud_config->getCloudContext(), 'k8s_node');
+    $cache_service->clearResourceDataCache($cloud_config->getCloudContext(), 'k8s_pod');
+  }
+
+  /**
+   * Implements hook_cloud_config_delete().
+   */
+  #[Hook('cloud_config_delete')]
+  public static function cloudConfigDelete(CloudConfig $cloud_config): void {
+    if ($cloud_config->bundle() === 'k8s') {
+      // Make sure to delete the cache to clear out the menu system.
+      \Drupal::service('cloud')->clearAllCacheValues();
+    }
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_config_k8s_edit_form_alter')]
+  public static function formCloudConfigK8sEditFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    k8s_form_cloud_config_k8s_form_common_alter($form, $form_state, $form_id);
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_config_k8s_add_form_alter')]
+  public static function formCloudConfigK8sAddFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    $form['cloud_context']['#access'] = FALSE;
+    $form['actions']['submit']['#submit'] = [
+      'k8s_form_cloud_config_k8s_add_form_submit',
+    ];
+    $form['#validate'][] = 'k8s_form_cloud_config_k8s_add_form_validate';
+    k8s_form_cloud_config_k8s_form_common_alter($form, $form_state, $form_id);
+  }
+
+  /**
+   * Implements hook_default_cloud_config_icon().
+   */
+  #[Hook('default_cloud_config_icon')]
+  public static function defaultCloudConfigIcon(EntityInterface $entity): ?int {
+    // Provides the calling hook with the default K8s icon.
+    return \Drupal::service('cloud')->getDefaultCloudConfigIcon($entity, 'k8s');
+  }
+
+  /**
+   * Implements hook_form_alter().
+   */
+  #[Hook('form_alter')]
+  public static function formAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    if (strpos($form_id, 'views_form_k8s_') === 0) {
+      $form['#submit'][] = 'cloud_views_bulk_form_submit';
+    }
+  }
+
+  /**
+   * Implements hook_query_TAG_Alter().
+   *
+   * Combined k8s_entity_views_access and ks8s_entity_views_access_with_namespace.
+   */
+  #[Hook('query_k8s_entity_views_access_alter')]
+  public static function queryK8sEntityViewsAccessAlter(AlterableInterface $query): void {
+    $route_name = \Drupal::routeMatch()->getRouteName();
+    // Regex supports the view.*.list and view.*.all pages.
+    if (!preg_match('/^view\.([a-z0-9_]+)\.(list|all)$/', $route_name, $matches)) {
+      return;
+    }
+    $entity_type = $matches[1];
+    $fields = \Drupal::service('entity_field.manager')->getFieldDefinitions($entity_type, $entity_type);
+    // Check whether there is namespace field in the entity type.
+    if (!empty($fields['namespace'])) {
+      k8s_build_namespace_query_condition($query);
+    }
+    // Add owner condition.
+    \Drupal::service('cloud')->buildOwnerQueryCondition($query);
+  }
+
+  /**
+   * Implements hook_query_TAG_Alter().
+   */
+  #[Hook('query_all_resources_views_access_alter')]
+  public static function queryAllResourcesViewsAccessAlter(AlterableInterface $query): void {
+    if (!$account = $query->getMetaData('account')) {
+      $account = \Drupal::currentUser();
+    }
+    $entities = \Drupal::service('plugin.manager.cloud_config_plugin')->loadConfigEntities('k8s');
+    $cloud_contexts = [];
+    foreach ($entities ?: [] as $entity) {
+      if ($account->hasPermission('view all cloud service providers') || $account->hasPermission('view ' . $entity->getCloudContext())) {
+        $cloud_contexts[] = $entity->getCloudContext();
+      }
+    }
+    $cloud_context_field = 'cloud_context';
+    if ($query->getMetaData('base_table')) {
+      $cloud_context_field = $query->getMetaData('base_table') . '.' . $cloud_context_field;
+    }
+    if (count($cloud_contexts)) {
+      $query->condition($cloud_context_field, $cloud_contexts, 'IN');
+    }
+    else {
+      // No permissions, do not let them view any cloud context.
+      // Return an empty page. This is just a catch-all. In
+      // normal cases, users will have access to certain cloud context.
+      $query->condition($cloud_context_field, '');
+    }
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_views_exposed_form_alter')]
+  public function formViewsExposedFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    // If not the view we are looking, move on.
+    if (strpos($form['#id'], 'views-exposed-form-k8s-') !== 0) {
+      return;
+    }
+    $route = \Drupal::routeMatch();
+    if ($route->getRouteName() === 'views.ajax') {
+      // When the request is from ajax, get the cloud context from referer.
+      global $base_url;
+      // Get the referer url.
+      $referer = \Drupal::request()->headers->get('referer');
+      if (!empty($referer)) {
+        // Get the alias or the referer.
+        $alias = substr($referer, strlen($base_url));
+        $url = Url::fromUri('internal:' . $alias);
+        $route_parameters = $url->getRouteParameters();
+        $cloud_context = $route_parameters['cloud_context'] ?? NULL;
+        $cloud_project_id = $route_parameters['cloud_project'] ?? NULL;
+      }
+    }
+    else {
+      $cloud_context = $route->getParameter('cloud_context');
+      $cloud_project_id = $route->getParameter('cloud_project');
+    }
+    $cloud_project = NULL;
+    $k8s_clusters = [];
+    if (!empty($cloud_project_id)) {
+      $cloud_project = \Drupal::entityTypeManager()->getStorage('cloud_project')->load($cloud_project_id);
+      if (!empty($cloud_project)) {
+        foreach ($cloud_project->get('field_k8s_clusters')->getValue() ?: [] as $k8s_cluster) {
+          $k8s_clusters[] = $k8s_cluster['value'];
+        }
+      }
+    }
+    if ((empty($cloud_context) || !empty($cloud_project_id)) && !empty($form['cloud_context'])) {
+      $account = \Drupal::currentUser();
+      $entities = \Drupal::service('plugin.manager.cloud_config_plugin')->loadConfigEntities('k8s');
+      $options = [];
+      foreach ($entities ?: [] as $entity) {
+        if ($account->hasPermission('view all cloud service providers') || $account->hasPermission('view ' . $entity->getCloudContext())) {
+          $options[$entity->getCloudContext()] = $entity->getName();
+        }
+      }
+      if (!empty($cloud_project) && !empty($options)) {
+        foreach ($options ?: [] as $key => $option) {
+          if (!in_array($key, $k8s_clusters)) {
+            unset($options[$key]);
+          }
+        }
+      }
+      $form['cloud_context'] = [
+        '#type' => 'select',
+        '#multiple' => FALSE,
+        '#empty_option' => $this->t('- Any -'),
+        '#options' => $options,
+        '#weight' => -60,
+      ];
+    }
+    // Query namespaces.
+    $storage = \Drupal::getContainer()->get('entity_type.manager')->getStorage('k8s_namespace');
+    // Gather namespaces sort by title.
+    $query = $storage->getQuery()->accessCheck(TRUE);
+    if (!empty($cloud_project_id)) {
+      $query = $query->condition('cloud_context', $k8s_clusters, 'IN');
+    }
+    elseif (!empty($cloud_context)) {
+      $query = $query->condition('cloud_context', $cloud_context);
+    }
+    $namespace_ids = $query->sort('name')->execute();
+    // If there are no namespaces, move on.
+    if (!$namespace_ids) {
+      return;
+    }
+    // Start building out the options for our select list.
+    $options = [];
+    $namespaces = $storage->loadMultiple($namespace_ids);
+    // Push titles into select list.
+    $account = \Drupal::currentUser();
+    foreach ($namespaces ?: [] as $namespace) {
+      if ($account->hasPermission('view any k8s namespace entities') || $account->hasPermission('view k8s namespace ' . $namespace->getName())) {
+        $options[$namespace->getName()] = $namespace->getName();
+      }
+    }
+    // Replace namespace item to select item.
+    if (!empty($form['namespace'])) {
+      $form['namespace'] = [
+        '#type' => 'select',
+        '#multiple' => FALSE,
+        '#empty_option' => $this->t('- Any -'),
+        '#options' => $options,
+      ];
+      // For all resource view.
+      if (empty($cloud_context) || !empty($cloud_project_id)) {
+        $cloud_context_namespaces = [];
+        foreach ($namespaces ?: [] as $namespace) {
+          $cloud_context_namespaces[$namespace->getCloudContext()][$namespace->getName()] = $namespace->getName();
+        }
+        $form['namespace']['#attached']['library'][] = 'k8s/k8s_all_resources';
+        $form['namespace']['#attached']['drupalSettings']['k8s']['cloud_context_namespaces'] = $cloud_context_namespaces;
+      }
+    }
+    // Query pods.
+    $storage = \Drupal::getContainer()->get('entity_type.manager')->getStorage('k8s_pod');
+    // Gather pods sort by title.
+    $query = $storage->getQuery()->accessCheck(TRUE);
+    if (!empty($cloud_project_id)) {
+      $query = $query->condition('cloud_context', $k8s_clusters, 'IN');
+    }
+    elseif (!empty($cloud_context)) {
+      $query = $query->condition('cloud_context', $cloud_context);
+    }
+    $pod_ids = $query->sort('status')->execute();
+    $pods = $storage->loadMultiple($pod_ids);
+    $options = [];
+    foreach ($pods ?: [] as $pod) {
+      $value = $pod->get('status')->value ?? NULL;
+      if (!empty($pod) && $value !== NULL && !array_key_exists($value, $options)) {
+        $options[$value] = $value;
+      }
+    }
+    // Replace pod status item to select item.
+    if (!empty($form['status'])) {
+      $form['status'] = [
+        '#type' => 'select',
+        '#multiple' => FALSE,
+        '#empty_option' => $this->t('- Any -'),
+        '#options' => $options,
+      ];
+    }
+  }
+
+  /**
+   * Implements hook_ENTITY_TYPE_delete().
+   */
+  #[Hook('k8s_namespace_delete')]
+  public static function k8sNamespaceDelete(K8sNamespace $namespace): void {
+    \Drupal::service('cloud')->invalidateCacheTags($namespace->getCacheTags());
+    // Delete cloud store entities and cache.
+    $k8s_service = \Drupal::service('k8s');
+    $k8s_service->setCloudContext($namespace->getCloudContext());
+    $k8s_service->deleteK8sCloudStore(0, [
+      'k8s_namespace_resource_store',
+      'k8s_cost_store',
+    ], $namespace->getName());
+    $cache_service = \Drupal::service('cloud.cache');
+    $cache_service->deleteResourceDataCache($namespace->getCloudContext(), 'k8s_pod', $namespace->getName());
+  }
+
+  /**
+   * Implements hook_entity_view_alter().
+   *
+   * @throws \Drupal\k8s\Service\K8sServiceException
+   *   Thrown when unable to get metrics nodes.
+   */
+  #[Hook('entity_view_alter')]
+  public function entityViewAlter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display): void {
+    $k8s_service = \Drupal::service('k8s');
+    $source_type = 'yml';
+    if ($entity->getEntityTypeId() === 'cloud_launch_template' && $entity->bundle() === 'k8s') {
+      if (!empty($build['field_git_access_token'][0]['#context']['value'])) {
+        $build['field_git_access_token'][0]['#context']['value'] = '********';
+        // If the source is git,
+        // change the label.
+        if (!empty($build['field_source_type'][0]['#markup'])) {
+          $source_type = $build['field_source_type'][0]['#markup'];
+          switch ($build['field_source_type'][0]['#markup']) {
+            // If the source is GitHub, change the label.
+            case 'Git':
+              $build['field_git_username']['#title'] = $this->t('Git username');
+              $build['field_git_access_token']['#title'] = $this->t('Git password');
+              break;
+
+            // If the source is GitHub, change the label.
+            case 'GitHub':
+              $build['field_git_username']['#title'] = $this->t('GitHub Username');
+              $build['field_git_access_token']['#title'] = $this->t('GitHub Access Token');
+              break;
+
+            // If the source is GitHub, change the label.
+            case 'GitLab':
+              $build['field_git_username']['#title'] = $this->t('GitLab Username');
+              $build['field_git_access_token']['#title'] = $this->t('GitLab Access Token');
+              break;
+          }
+        }
+      }
+      // Configure Source fields.
+      $configuration_fields = [
+            [
+              'name' => 'source',
+              'title' => $this->t('Source'),
+              'open' => TRUE,
+              'fields' => [
+                'field_source_type',
+                'field_git_repository',
+              ],
+            ],
+            [
+              'name' => 'credentials',
+              'title' => $this->t('Credentials'),
+              'open' => TRUE,
+              'fields' => [
+                'field_git_username',
+                'field_git_access_token',
+              ],
+            ],
+            [
+              'name' => 'branch',
+              'title' => $this->t('Branch'),
+              'open' => TRUE,
+              'fields' => [
+                'field_git_branch',
+                'field_git_path',
+              ],
+            ],
+            [
+              'name' => 'details',
+              'title' => $this->t('Details'),
+              'open' => [
+                'textarea[name="field_detail[0][value]"]' => [
+                        [
+                          '!value' => '',
+                        ],
+                ],
+              ],
+              'fields' => [
+                'field_yaml_url',
+                'field_detail',
+              ],
+            ],
+      ];
+      \Drupal::service('cloud')->reorderForm($build, $configuration_fields);
+      $is_metrics_server = empty($entity) || empty($entity->hasField('field_is_metrics_server')) || empty($entity->get('field_is_metrics_server')) ? FALSE : $entity->field_is_metrics_server->value;
+      $source_type = $entity->get('field_source_type')->getValue();
+      \Drupal::service('cloud')->reorderForm($build, k8s_launch_template_field_orders(FALSE, $is_metrics_server, FALSE, str_contains($source_type[0]['value'], 'git')));
+      $build['k8s']['cloud_context'] = $entity->cloud_context->view();
+      $build['k8s']['cloud_context']['#title'] = $this->t('K8s cluster');
+      $build['k8s']['cloud_context']['#label_display'] = 'inline';
+      $values = $k8s_service->clusterAllowedValues();
+      $build['k8s']['cloud_context'][0]['#context']['value'] = $values[$build['k8s']['cloud_context'][0]['#context']['value']];
+      $build['k8s']['field_object']['#title'] = $this->t('Kind');
+      if (!empty($build['configuration_git']['source']['field_source_type']) && !str_contains($build['configuration_git']['source']['field_source_type'][0]['#markup'], 'Git') || empty($build['configuration_git']['credentials']['field_git_access_token'][0]['#context']['value']) && empty($build['configuration_git']['credentials']['field_git_username'][0]['#context']['value'])) {
+        unset($build['configuration_git']['credentials']);
+      }
+      if (empty($build['configuration_yml']['details']['field_detail'][0]['#markup'])) {
+        unset($build['configuration_yml']['details']);
+      }
+      // Check if field_launch_template_parameters is empty,
+      // and remove fieldset if it is.
+      if (empty($build['launch_template_parameters']['field_launch_template_parameters'][0])) {
+        unset($build['launch_template_parameters']);
+      }
+      $git_available = $k8s_service->isGitCommandAvailable();
+      if (!$git_available && str_contains($source_type[0]['value'], 'git')) {
+        $messages = \Drupal::messenger()->messagesByType(\Drupal::messenger()::TYPE_ERROR);
+        $warning = $this->t('A git command could not be found on the server.  Install the git command and try again.');
+        $message = Markup::create((string) $warning);
+        if (!in_array($message, $messages)) {
+          \Drupal::messenger()->addWarning($warning);
+        }
+      }
+      if ($build['time_scheduler']['field_enable_time_scheduler']['0']['#markup'] === 'Off') {
+        unset($build['time_scheduler']);
+      }
+      else {
+        $startup_hour = $build['time_scheduler']['field_startup_time_hour'][0]['#markup'];
+        $startup_minute = $build['time_scheduler']['field_startup_time_minute'][0]['#markup'];
+        $stop_hour = $build['time_scheduler']['field_stop_time_hour'][0]['#markup'];
+        $stop_minute = $build['time_scheduler']['field_stop_time_minute'][0]['#markup'];
+        $build['time_scheduler']['field_enable_time_scheduler']['#label_display'] = 'inline';
+        $build['time_scheduler']['field_startup_time_hour']['#title'] = 'Start-up time';
+        $build['time_scheduler']['field_startup_time_hour']['#label_display'] = 'inline';
+        $build['time_scheduler']['field_startup_time_hour'][0]['#markup'] = $startup_hour . ':' . $startup_minute;
+        $build['time_scheduler']['field_stop_time_hour']['#title'] = 'Stop time';
+        $build['time_scheduler']['field_stop_time_hour']['#label_display'] = 'inline';
+        $build['time_scheduler']['field_stop_time_hour'][0]['#markup'] = $stop_hour . ':' . $stop_minute;
+        unset($build['time_scheduler']['field_startup_time_minute']);
+        unset($build['time_scheduler']['field_stop_time_minute']);
+      }
+    }
+    if (in_array($entity->getEntityTypeId(), [
+      'k8s_node',
+      'k8s_pod',
+    ]) && !empty($entity->getCloudContext())) {
+      // Confirm whether the metrics API can be used or not.
+      $metrics_enabled = TRUE;
+      if (!empty($entity->getCloudContext())) {
+        $k8s_service->setCloudContext($entity->getCloudContext());
+        try {
+          $k8s_service->getMetricsNodes();
+        }
+        catch (K8sServiceException $e) {
+          $metrics_enabled = FALSE;
+        }
+      }
+      if (!empty($build['entity_metrics'])) {
+        $build['entity_metrics']['k8s_entity_metrics'] = [
+          '#markup' => '<div id="k8s_entity_metrics"></div>',
+        ];
+        $build['#attached']['library'][] = 'k8s/k8s_entity_metrics';
+      }
+      $build['#attached']['drupalSettings']['k8s']['metrics_enabled'] = $metrics_enabled;
+      $config = \Drupal::config('k8s.settings');
+      $build['#attached']['drupalSettings']['k8s']['k8s_js_refresh_interval'] = $config->get('k8s_js_refresh_interval');
+    }
+    if (!empty($build['node_pods'])) {
+      $build['node_pods']['k8s_node_pods'] = [
+        '#type' => 'view',
+        '#name' => 'k8s_pod',
+        '#display_id' => 'block_for_node',
+      ];
+    }
+  }
+
+  /**
+   * Implements hook_ENTITY_TYPE_presave().
+   */
+  #[Hook('cloud_launch_template_presave')]
+  public static function cloudLaunchTemplatePresave(EntityInterface $entity): void {
+    if (empty($entity) || $entity->bundle() !== 'k8s') {
+      return;
+    }
+    // If there is no info stored in field_detail, return.
+    // Otherwise, use the K8s service to decode yaml string.
+    if (empty($entity->field_detail->value)) {
+      return;
+    }
+    $k8s_service = \Drupal::service('k8s');
+    $yamls = $k8s_service->decodeMultipleDocYaml($entity->field_detail->value);
+    if (count($yamls) > 1) {
+      $entity->field_object->value = 'mixed';
+      return;
+    }
+    $yaml = $yamls[0];
+    // Set the object to NULL unless the yaml file provides a kind.
+    // Based on the ['kind'] key, search for supported templates.
+    $entity->field_object->value = NULL;
+    if (!empty($yaml['kind'])) {
+      $templates = $k8s_service->supportedCloudLaunchTemplates();
+      $object = array_search($yaml['kind'], $templates);
+      // The field 'field_object' should only be
+      // assigned the value of $object when the user selects 'yml'.
+      $entity->field_object->value = !empty($object) ? $object : NULL;
+    }
+  }
+
+  /**
+   * Implements hook_form_BASE_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_launch_template_k8s_add_form_alter')]
+  public static function formCloudLaunchTemplateK8sAddFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    $form['field_object']['#access'] = FALSE;
+    $form['field_launch_resources']['#access'] = FALSE;
+    k8s_form_cloud_launch_template_k8s_form_common_alter($form, $form_state, $form_id);
+    $index = array_search('::save', $form['actions']['submit']['#submit']);
+    array_splice($form['actions']['submit']['#submit'], $index, 0, 'k8s_form_cloud_launch_template_k8s_form_submit');
+    $form['actions']['submit']['#submit'][] = 'k8s_form_cloud_launch_template_k8s_after_save';
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_launch_template_k8s_edit_form_alter')]
+  public function formCloudLaunchTemplateK8sEditFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    $form['field_object']['#access'] = FALSE;
+    $form['field_launch_resources']['#access'] = FALSE;
+    $form['old_yaml_url'] = [
+      '#type' => 'hidden',
+      '#value' => $form['field_yaml_url']['widget'][0]['uri']['#default_value'],
+      '#disabled' => TRUE,
+    ];
+    $form['old_detail'] = [
+      '#type' => 'hidden',
+      '#value' => $form['field_detail']['widget'][0]['value']['#default_value'],
+      '#disabled' => TRUE,
+    ];
+    k8s_form_cloud_launch_template_k8s_form_common_alter($form, $form_state, $form_id);
+    // Hide new revision checkbox.
+    $form['new_revision']['#access'] = FALSE;
+    $cloud_context = $form['others']['cloud_context'];
+    unset($form['others']['cloud_context']);
+    $k8s_service = \Drupal::service('k8s');
+    $cloud_context['#disabled'] = FALSE;
+    $cloud_context['widget'][0]['value']['#title'] = $this->t('Cluster');
+    $cloud_context['widget'][0]['value']['#type'] = 'select';
+    $cloud_context['widget'][0]['value']['#options'] = $k8s_service->clusterAllowedValues();
+    $cloud_context['widget'][0]['value']['#ajax'] = [
+      'callback' => 'k8s_ajax_callback_get_fields',
+    ];
+    unset($cloud_context['widget'][0]['value']['#size']);
+    unset($cloud_context['widget'][0]['value']['#description']);
+    $cloud_context['#weight'] = 1;
+    $form['k8s']['cloud_context'] = $cloud_context;
+    $form['#entity_builders'][] = 'k8s_form_cloud_launch_template_k8s_edit_entity_builder';
+    $index = array_search('::save', $form['actions']['submit']['#submit']);
+    array_splice($form['actions']['submit']['#submit'], $index, 0, 'k8s_form_cloud_launch_template_k8s_form_submit');
+    $form['actions']['submit']['#submit'][] = 'k8s_form_cloud_launch_template_k8s_after_save';
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_launch_template_k8s_copy_form_alter')]
+  public function formCloudLaunchTemplateK8sCopyFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    $form['field_object']['#access'] = FALSE;
+    $form['field_launch_resources']['#access'] = FALSE;
+    $form['old_yaml_url'] = [
+      '#type' => 'hidden',
+      '#value' => $form['field_yaml_url']['widget'][0]['uri']['#default_value'],
+      '#disabled' => TRUE,
+    ];
+    $form['old_detail'] = [
+      '#type' => 'hidden',
+      '#value' => $form['field_detail']['widget'][0]['value']['#default_value'],
+      '#disabled' => TRUE,
+    ];
+    k8s_form_cloud_launch_template_k8s_form_common_alter($form, $form_state, $form_id);
+    $name = $form['k8s']['name']['widget'][0]['value']['#default_value'];
+    $form['k8s']['name']['widget'][0]['value']['#default_value'] = $this->t('copy_of_@name', [
+      '@name' => $name,
+    ]);
+    // Hide new revision checkbox.
+    $form['new_revision']['#access'] = FALSE;
+    // Clear the revision log message.
+    $form['others']['revision_log_message']['widget'][0]['value']['#default_value'] = NULL;
+    // Change value of the submit button.
+    $form['actions']['submit']['#value'] = $this->t('Copy');
+    // Delete the delete button.
+    $form['actions']['delete']['#access'] = FALSE;
+    $form['#entity_builders'][] = 'k8s_form_cloud_launch_template_k8s_edit_entity_builder';
+    $form['actions']['submit']['#submit'][1] = 'k8s_form_cloud_launch_template_k8s_form_submit';
+    $form['actions']['submit']['#submit'][2] = 'k8s_form_cloud_launch_template_k8s_copy_form_submit';
+  }
+
+  /**
+   * Implements hook_entity_bundle_field_info_alter().
+   */
+  #[Hook('entity_bundle_field_info_alter')]
+  public static function entityBundleFieldInfoAlter(array &$fields, EntityTypeInterface $entity_type, $bundle): void {
+    if ($bundle === 'k8s') {
+      if (!empty($fields['field_detail'])) {
+        // Use the ID as defined in the annotation of the constraint definition.
+        $fields['field_detail']->addConstraint('yaml_array_data', []);
+        $fields['field_detail']->addConstraint('yaml_object_support', []);
+      }
+      if (!empty($fields['field_git_path'])) {
+        $fields['field_git_path']->addPropertyConstraints('value', [
+          'Regex' => [
+            'pattern' => '/^\//i',
+          ],
+        ]);
+      }
+    }
+  }
+
+  /**
+   * Implements hook_cron().
+   */
+  #[Hook('cron')]
+  public static function cron(): void {
+    $k8s_service = \Drupal::service('k8s');
+    // Update resources.
+    $config_entities = \Drupal::service('plugin.manager.cloud_config_plugin')->loadConfigEntities('k8s');
+    foreach ($config_entities ?: [] as $config_entity) {
+      if ($config_entity->isRemote()) {
+        continue;
+      }
+      $k8s_service->setCloudContext($config_entity->getCloudContext());
+      $k8s_service->createResourceQueueItems();
+    }
+    // Update resources according to schedule entities.
+    foreach ($config_entities ?: [] as $config_entity) {
+      if ($config_entity->isRemote()) {
+        continue;
+      }
+      $k8s_service->setCloudContext($config_entity->getCloudContext());
+      $k8s_service->updateNodesWithoutBatch();
+      $k8s_service->updatePodsWithoutBatch();
+      $k8s_service->updateScheduledResources();
+    }
+    $plugin_manager = \Drupal::service('plugin.manager.cloud_store_plugin');
+    $plugin = NULL;
+    try {
+      $plugin = $plugin_manager->loadPluginVariant('k8s_cost_store');
+    }
+    catch (PluginNotFoundException $e) {
+      \Drupal::service('cloud')->handleException($e);
+      return;
+    }
+    // Delete outdated resource stores.
+    $plugin->deleteK8sResourceStoreEntity();
+    // Update resource stores.
+    $plugin->updateK8sResourceStoreEntity();
+    // Update cost stores.
+    $plugin->updateCostStoreEntity();
+  }
+
+  /**
+   * Implements hook_ENTITY_TYPE_predelete().
+   */
+  #[Hook('user_predelete')]
+  public static function userPredelete($account): void {
+    /** @var Drupal\cloud\Service\CloudService $cloud_service */
+    $cloud_service = \Drupal::service('cloud');
+    $entity_types = $cloud_service->getProviderEntityTypes('k8s');
+    foreach ($entity_types ?: [] as $entity_type) {
+      if (empty($entity_type)) {
+        continue;
+      }
+      $ids = \Drupal::entityTypeManager()->getStorage($entity_type->id())->getQuery()->accessCheck(TRUE)->condition('uid', $account->id())->execute();
+      if (count($ids) === 0) {
+        continue;
+      }
+      $cloud_service->reassignUids($ids, [
+        'uid' => 0,
+      ], $entity_type->id(), FALSE, $account, [], 'k8s_reassign_entity_uid_callback');
+    }
+  }
+
+  /**
+   * Implements hook_ENTITY_TYPE_view_alter().
+   */
+  #[Hook('cloud_config_view_alter')]
+  public static function cloudConfigViewAlter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display): void {
+    if ($entity->bundle() === 'k8s') {
+      $map_json_url = \Drupal::config('cloud.settings')->get('cloud_custom_location_map_json_url');
+      $url = Url::fromRoute('entity.cloud_config.location', [
+        'cloud_config' => $entity->id(),
+      ])->toString();
+      $build['cloud_config_location_map'] = [
+        '#markup' => '<div id="cloud_config_location"></div>',
+        '#attached' => [
+          'library' => [
+            'cloud/cloud_config_location',
+          ],
+          'drupalSettings' => [
+            'cloud' => [
+              'cloud_location_map_json_url' => $map_json_url,
+              'cloud_config_location_json_url' => $url,
+            ],
+          ],
+        ],
+      ];
+      $build['field_location_country']['#access'] = FALSE;
+      $build['field_location_city']['#access'] = FALSE;
+      $build['field_location_longitude']['#access'] = FALSE;
+      $build['field_location_latitude']['#access'] = FALSE;
+      k8s_cloud_config_fieldsets($build);
+    }
+  }
+
+  /**
+   * Implements hook_ENTITY_TYPE_create().
+   */
+  #[Hook('k8s_pod_create')]
+  public static function k8sPodCreate(EntityInterface $entity): void {
+    k8s_update_node_for_pod($entity);
+  }
+
+  /**
+   * Implements hook_ENTITY_TYPE_update().
+   */
+  #[Hook('k8s_pod_update')]
+  public static function k8sPodUpdate(EntityInterface $entity): void {
+    k8s_update_node_for_pod($entity);
+  }
+
+  /**
+   * Implements hook_ENTITY_TYPE_delete().
+   */
+  #[Hook('k8s_pod_delete')]
+  public static function k8sPodDelete(EntityInterface $entity): void {
+    k8s_update_node_for_pod($entity);
+    // Delete cloud store entities and cache.
+    $k8s_service = \Drupal::service('k8s');
+    $k8s_service->setCloudContext($entity->getCloudContext());
+    $k8s_service->deleteK8sCloudStore(0, [
+      'k8s_pod_resource_store',
+    ], "{$entity->getNamespace()}:{$entity->getName()}");
+    $cache_service = \Drupal::service('cloud.cache');
+    $cache_service->deleteResourceDataCache($entity->getCloudContext(), $entity->bundle(), $entity->getNamespace(), $entity->getName());
+  }
+
+  /**
+   * Implements hook_preprocess_field().
+   */
+  #[Hook('preprocess_field')]
+  public static function preprocessField(&$variables): void {
+    if ($variables['element']['#object']->getEntityTypeId() === 'cloud_launch_template') {
+      $name = $variables['element']['#field_name'];
+      if ($name === 'field_object' && count($variables['items']) > 1) {
+        $objects = [];
+        foreach ($variables['items'] ?: [] as $idx => &$item) {
+          $object = $item['content']['#markup'];
+          if (!empty($objects[$object])) {
+            ++$objects[$object];
+            unset($variables['items'][$idx]);
+          }
+          else {
+            $objects[$object] = 1;
+          }
+        }
+        foreach ($variables['items'] ?: [] as $idx => &$item) {
+          $object = $item['content']['#markup'];
+          $count = $objects[$object];
+          if ($count > 1) {
+            $item['content']['#markup'] = "{$count} {$object}" . 's';
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Implements hook_form_BASE_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_launch_template_k8s_delete_form_alter')]
+  public function formCloudLaunchTemplateK8sDeleteFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    $server_template = $form_state->getFormObject()->getEntity();
+    $resources = $server_template->get('field_launch_resources')->getValue();
+    if (!empty($resources)) {
+      $form['field_delete_option'] = [
+        '#type' => 'radios',
+        '#options' => [
+          'both' => $this->t('Delete both application and resources'),
+          'resources' => $this->t('Delete only resources'),
+          'application' => $this->t('Delete only application'),
+        ],
+        '#title' => $this->t('Delete Options'),
+        '#default_value' => 'both',
+      ];
+      \Drupal::messenger()->addWarning($this->t("Make sure the following resources will be deleted if you select <em>'Delete both application and resources'</em> or <em>'Delete only resources'</em> option."));
+      k8s_create_resources_message($form, $resources);
+      $index = array_search('::submitForm', $form['actions']['submit']['#submit']);
+      array_splice($form['actions']['submit']['#submit'], $index, 1, 'k8s_form_cloud_launch_template_k8s_delete_form_submit');
+    }
+  }
+
+  /**
+   * Implements hook_form_BASE_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_launch_template_k8s_launch_form_alter')]
+  public static function formCloudLaunchTemplateK8sLaunchFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    if (empty($form['actions']['submit']['#submit'])) {
+      return;
+    }
+    $index = array_search('::submitForm', $form['actions']['submit']['#submit']);
+    array_splice($form['actions']['submit']['#submit'], $index, 1, 'k8s_form_cloud_launch_template_k8s_launch_form_submit');
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_launch_template_k8s_review_form_alter')]
+  public static function formCloudLaunchTemplateK8sReviewFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    $index = array_search('::save', $form['actions']['submit']['#submit']);
+    array_splice($form['actions']['submit']['#submit'], $index, 0, 'k8s_form_cloud_launch_template_k8s_review_form_submit');
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_launch_template_k8s_approve_form_alter')]
+  public static function formCloudLaunchTemplateK8sApproveFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    $index = array_search('::save', $form['actions']['submit']['#submit']);
+    array_splice($form['actions']['submit']['#submit'], $index, 0, 'k8s_form_cloud_launch_template_k8s_approve_form_submit');
+  }
+
+  /**
+   * Implements hook_entity_delete().
+   */
+  #[Hook('entity_delete')]
+  public static function entityDelete(EntityInterface $entity): void {
+    if ($entity instanceof K8sEntityInterface && !empty($entity->getAnnotations())) {
+      $annotations = $entity->getAnnotations();
+      if (!empty($annotations[K8sEntityInterface::ANNOTATION_LAUNCHED_APPLICATION_ID])) {
+        $server_template_id = $annotations[K8sEntityInterface::ANNOTATION_LAUNCHED_APPLICATION_ID];
+        $server_template = \Drupal::entityTypeManager()->getStorage('cloud_launch_template')->load($server_template_id);
+        if (!empty($server_template && $server_template->hasField('field_launch_resources'))) {
+          $resources = $server_template->get('field_launch_resources')->getValue();
+          if (!empty($resources)) {
+            foreach ($resources ?: [] as $idx => $resource) {
+              $type = $resource['item_key'];
+              $id = $resource['item_value'];
+              if ($type === $entity->bundle() && $id === $entity->id()) {
+                $server_template->get('field_launch_resources')->removeItem($idx);
+                $server_template->setValidationRequired(FALSE);
+                $server_template->save();
+                break;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Implements hook_ENTITY_TYPE_delete().
+   */
+  #[Hook('cloud_project_delete')]
+  public function cloudProjectDelete(EntityInterface $entity): void {
+    if ($entity->bundle() !== 'k8s' || $entity->getEntityType()->id() !== 'cloud_project') {
+      return;
+    }
+    $k8s_clusters = $entity->get('field_k8s_clusters');
+    $messenger = \Drupal::messenger();
+    $k8s_cluster_list = [];
+    foreach ($k8s_clusters ?: [] as $cloud_context) {
+      if (!empty($cloud_context->value)) {
+        $k8s_cluster_list[$cloud_context->value] = $cloud_context->value;
+      }
+    }
+    $k8s_resource_list = [
+      'k8s_namespace',
+      'k8s_resource_quota',
+    ];
+    try {
+      k8s_delete_specific_resource($k8s_cluster_list, $k8s_resource_list, $entity->getName());
+      // Delete the role if it exists.
+      $roles = \Drupal::entityTypeManager()->getStorage('user_role')->loadByProperties([
+        'id' => $entity->getName(),
+      ]);
+      if (!empty($roles)) {
+        $role = array_shift($roles);
+        $role->delete();
+        \Drupal::service('cloud')->processOperationStatus($role, 'deleted');
+      }
+      $message_all = $messenger->all();
+      $messages = array_shift($message_all);
+      $messenger->deleteAll();
+      $output = '';
+      $search = "/ {$entity->getName()} /";
+      foreach ($messages ?: [] as $message) {
+        $string = $message->jsonSerialize();
+        if (preg_match($search, $string)) {
+          $output .= "<li>{$message}</li>";
+        }
+        else {
+          $messenger->addStatus($message);
+        }
+      }
+      $messenger->addStatus($this->t('The @type @label has been deleted.<ul>@output</ul>', [
+        '@type' => $entity->getEntityType()->getSingularLabel(),
+        '@label' => $entity->label(),
+        '@output' => Markup::Create($output),
+      ]));
+      \Drupal::logger('k8s')->info($this->t('@type: deleted %label.', [
+        '@type' => $entity->getEntityType()->getSingularLabel(),
+        '%label' => $entity->label(),
+      ]));
+    }
+    catch (EntityStorageException $e) {
+      try {
+        \Drupal::service('cloud')->processOperationErrorStatus($entity, 'deleted');
+      }
+      catch (EntityMalformedException $e) {
+        \Drupal::service('cloud')->handleException($e);
+      }
+    }
+  }
+
+  /**
+   * Implements hook_form_BASE_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_project_k8s_add_form_alter')]
+  public static function formCloudProjectK8sAddFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    k8s_form_cloud_project_k8s_form_common_alter($form, $form_state, $form_id);
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_project_k8s_edit_form_alter')]
+  public static function formCloudProjectK8sEditFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    k8s_form_cloud_project_k8s_form_common_alter($form, $form_state, $form_id);
+    // Hide new revision checkbox.
+    $form['new_revision']['#access'] = FALSE;
+    $form['project']['name']['#access'] = FALSE;
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_project_k8s_copy_form_alter')]
+  public function formCloudProjectK8sCopyFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    k8s_form_cloud_project_k8s_form_common_alter($form, $form_state, $form_id);
+    // Clear the revision log message.
+    $form['others']['revision_log_message']['widget'][0]['value']['#default_value'] = NULL;
+    // Change value of the submit button.
+    $form['actions']['submit']['#value'] = $this->t('Copy');
+    // Delete the delete button.
+    $form['actions']['delete']['#access'] = FALSE;
+    $form['actions']['submit']['#submit'][1] = 'k8s_form_cloud_project_k8s_copy_form_submit';
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_project_k8s_launch_form_alter')]
+  public function formCloudProjectK8sLaunchFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    $entity = $form_state->getFormObject()->getEntity();
+    $fieldset_defs = k8s_project_field_orders();
+    $entity_type = $entity->getEntityTypeId();
+    $bundle = $entity->bundle();
+    $field_definitions = \Drupal::service('entity_field.manager')->getFieldDefinitions($entity_type, $bundle);
+    $weight = 4;
+    foreach ($fieldset_defs ?: [] as $fieldset_def) {
+      $fieldset_name = $fieldset_def['name'];
+      $form[$fieldset_name] = [
+        '#type' => 'details',
+        '#title' => $fieldset_def['title'],
+        '#weight' => $weight++,
+        '#open' => $fieldset_def['open'],
+      ];
+      foreach ($fieldset_def['fields'] ?: [] as $field_name) {
+        if (!$entity->hasField($field_name) || empty($field_name)) {
+          continue;
+        }
+        $label = FieldConfig::loadByName($entity_type, $bundle, $field_name);
+        $values = array_column($entity->get($field_name)->getValue(), 'value');
+        if (!empty($label)) {
+          $form[$fieldset_name][$field_name]['widget'][0] = [
+            '#label_display' => 'inline',
+            '#type' => 'item',
+            '#title' => $label->getLabel() . ':',
+            '#markup' => implode(", ", $values),
+          ];
+        }
+        else {
+          $form[$fieldset_name][$field_name]['widget'][0] = [
+            '#label_display' => 'inline',
+            '#type' => 'item',
+            '#title' => $field_definitions[$field_name]->getLabel() . ':',
+            '#markup' => implode(", ", $values),
+          ];
+        }
+        $form[$fieldset_name][$field_name]['#weight'] = $weight++;
+      }
+    }
+    $user = User::load(\Drupal::currentUser()->id());
+    $form['username'] = [
+      '#type' => 'details',
+      '#title' => $this->t('Username'),
+      '#weight' => $weight++,
+      '#open' => TRUE,
+    ];
+    $form['username']['launch_user'] = [
+      '#type' => 'entity_autocomplete',
+      '#target_type' => 'user',
+      '#title' => $this->t('Launch as'),
+      '#size' => 60,
+      '#default_value' => $user,
+      '#weight' => 0,
+      '#required' => TRUE,
+      '#selection_handler' => 'default:launch_project_user',
+      '#element_validate' => [
+              [
+                EntityAutocomplete::class,
+                'validateEntityAutocomplete',
+              ],
+              [
+                LaunchProjectUserSelection::class,
+                'validateUser',
+              ],
+      ],
+    ];
+    unset($form['others']);
+    if ($form['resource_scheduler']['field_enable_resource_scheduler']['widget'][0]['#markup'] === '0') {
+      unset($form['resource_scheduler']);
+    }
+    else {
+      $form['resource_scheduler']['field_enable_resource_scheduler']['widget'][0]['#markup'] = 'On';
+    }
+  }
+
+  /**
+   * Implements hook_form_BASE_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_store_form_alter')]
+  public function formCloudStoreFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    // Get operation.
+    $operation = $form_state->getBuildInfo()['callback_object']->getOperation();
+    // To check whether cloud_context exists or not.
+    if (!empty($form['store']['cloud_context'])) {
+      $k8s_service = \Drupal::service('k8s');
+      $cloud_context = $form['store']['cloud_context'];
+      $cloud_context['widget'][0]['value']['#type'] = 'select';
+      $cloud_context['widget'][0]['value']['#options'] = $k8s_service->clusterAllowedValues();
+      $cloud_context['widget'][0]['value']['#ajax'] = [
+        'callback' => 'k8s_ajax_callback_get_fields',
+      ];
+      unset($cloud_context['widget'][0]['value']['#size']);
+      $cloud_context['#weight'] = 1;
+      $form['store']['cloud_context'] = $cloud_context;
+    }
+    // To change name for copy_form.
+    switch ($operation) {
+      case 'edit':
+      case 'add':
+        break;
+
+      case 'copy':
+        // Change value of the submit button.
+        $form['actions']['submit']['#value'] = $this->t('Copy');
+        // Delete the delete button.
+        $form['actions']['delete']['#access'] = FALSE;
+        $form['actions']['submit']['#submit'][1] = 'k8s_form_cloud_store_k8s_copy_form_submit';
+        break;
+    }
+  }
+
+  /**
+   * Implements hook_ENTITY_TYPE_delete().
+   */
+  #[Hook('k8s_node_delete')]
+  public static function k8sNodeDelete(K8sNode $node): void {
+    // Delete cloud store entities and cache.
+    $k8s_service = \Drupal::service('k8s');
+    $k8s_service->setCloudContext($node->getCloudContext());
+    $k8s_service->deleteK8sCloudStore(0, [
+      'k8s_node_resource_store',
+    ], $node->getName());
+    $cache_service = \Drupal::service('cloud.cache');
+    $cache_service->deleteResourceDataCache($node->getCloudContext(), $node->bundle(), $node->getName());
+  }
+
+  /**
+   * Implements hook_rebuild().
+   */
+  #[Hook('rebuild')]
+  public static function rebuild(): void {
+    // Rebuild the latest created time cache.
+    $cache_service = \Drupal::service('cloud.cache');
+    $max_alias = 'max_created';
+    $results = \Drupal::entityTypeManager()->getStorage('cloud_store')->getAggregateQuery()->accessCheck(TRUE)->aggregate('created', 'MAX', NULL, $max_alias)->condition('type', [
+      'k8s_namespace_resource_store',
+      'k8s_node_resource_store',
+      'k8s_pod_resource_store',
+    ], 'IN')->groupBy('type')->groupBy('cloud_context')->groupBy('name')->execute();
+    if (empty($results)) {
+      return;
+    }
+    foreach ($results ?: [] as $data) {
+      $cache_service->setLatestCreatedTimeCache($data['cloud_context'], $data['type'], $data['name'], $data[$max_alias]);
+    }
+  }
+
+}
diff --git a/modules/cloud_service_providers/k8s/src/Hook/K8sTokensHooks.php b/modules/cloud_service_providers/k8s/src/Hook/K8sTokensHooks.php
new file mode 100644
index 0000000..13ab5b2
--- /dev/null
+++ b/modules/cloud_service_providers/k8s/src/Hook/K8sTokensHooks.php
@@ -0,0 +1,90 @@
+<?php
+
+namespace Drupal\k8s\Hook;
+
+use Drupal\Core\Render\BubbleableMetadata;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for k8s.
+ */
+class K8sTokensHooks {
+  use StringTranslationTrait;
+
+  /**
+   * Implements hook_token_info().
+   */
+  #[Hook('token_info')]
+  public function tokenInfo(): array {
+    $types['k8s_launch_template'] = [
+      'name' => $this->t('K8s Launch Template'),
+      'description' => $this->t('Tokens related to individual K8s launch template.'),
+      'needs-data' => 'k8s_launch_template',
+    ];
+    $launch_template['name'] = [
+      'name' => $this->t('List of launch template name'),
+      'description' => $this->t('Enter the name of the launch template entity.'),
+    ];
+    $launch_template['launch_template_link'] = [
+      'name' => $this->t('Launch Template Link'),
+      'description' => $this->t('An absolute link to launch template.'),
+    ];
+    $launch_template['launch_template_edit_link'] = [
+      'name' => $this->t('Launch Template Edit Link'),
+      'description' => $this->t('An absolute link to edit the launch template.'),
+    ];
+    $launch_template['changed'] = [
+      'name' => $this->t('Change date'),
+      'description' => $this->t('The launch template change date.'),
+    ];
+    $types['k8s_launch_template_email'] = [
+      'name' => $this->t('K8s Launch Template Email'),
+      'description' => $this->t('Tokens related to individual K8s launch template email.'),
+      'needs-data' => 'k8s_launch_template_email',
+    ];
+    $launch_template_email['launch_templates'] = [
+      'name' => $this->t('List of launch templates'),
+      'description' => $this->t('List of launch templates to display to a user.'),
+    ];
+    $types['k8s_launch_template_request_email'] = [
+      'name' => $this->t('K8s Launch Template Request Email'),
+      'description' => $this->t('Tokens related to individual K8s launch template request email.'),
+      'needs-data' => 'k8s_launch_template_request_email',
+    ];
+    $launch_template_request_email['launch_templates_request'] = [
+      'name' => $this->t('List of request launch templates'),
+      'description' => $this->t('List of request launch templates to display to a user.'),
+    ];
+    return [
+      'types' => $types,
+      'tokens' => [
+        'k8s_launch_template' => $launch_template,
+        'k8s_launch_template_email' => $launch_template_email,
+        'k8s_launch_template_request_email' => $launch_template_request_email,
+      ],
+    ];
+  }
+
+  /**
+   * Implements hook_tokens().
+   */
+  #[Hook('tokens')]
+  public static function tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata): array {
+    $replacements = [];
+    if ($type === 'k8s_launch_template' && !empty($data['k8s_launch_template'])) {
+      $replacements = k8s_launch_template_tokens($tokens, $data);
+    }
+    elseif ($type === 'k8s_launch_template_request' && !empty($data['k8s_launch_template_request'])) {
+      $replacements = k8s_launch_template_request_tokens($tokens, $data);
+    }
+    elseif ($type === 'k8s_launch_template_email') {
+      $replacements = k8s_launch_template_email_tokens($tokens, $data);
+    }
+    elseif ($type === 'k8s_launch_template_request_email') {
+      $replacements = k8s_launch_template_request_email_tokens($tokens, $data);
+    }
+    return $replacements;
+  }
+
+}
diff --git a/modules/cloud_service_providers/openstack/openstack.module b/modules/cloud_service_providers/openstack/openstack.module
index 6cbcafc..825c9bd 100644
--- a/modules/cloud_service_providers/openstack/openstack.module
+++ b/modules/cloud_service_providers/openstack/openstack.module
@@ -7,12 +7,12 @@
  * This module handles UI interactions with the OpenStack.
  */
 
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\openstack\Hook\OpenstackHooks;
 use Drupal\Component\Serialization\Yaml;
 use Drupal\Core\Ajax\AjaxResponse;
 use Drupal\Core\Ajax\ReplaceCommand;
 use Drupal\Core\Database\Query\AlterableInterface;
-use Drupal\Core\Database\Query\Condition;
-use Drupal\Core\Datetime\DrupalDateTime;
 use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
 use Drupal\Core\Entity\EntityInterface;
@@ -20,7 +20,6 @@ use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Render\Markup;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\StringTranslation\TranslatableMarkup;
-use Drupal\Core\Url;
 use Drupal\cloud\Entity\CloudConfig;
 use Drupal\cloud\Entity\CloudConfigInterface;
 use Drupal\cloud\Entity\CloudLaunchTemplate;
@@ -30,43 +29,21 @@ use Drupal\file\Entity\File;
 use Drupal\file\FileInterface;
 use Drupal\openstack\Service\Rest\OpenStackService as OpenStackRestService;
 use Drupal\user\Entity\User;
-use Drupal\views\Views;
 
 /**
  * Implements hook_help().
  */
+#[LegacyHook]
 function openstack_help($route_name, RouteMatchInterface $route_match): string {
-  $output = '';
-  switch ($route_name) {
-    case 'help.page.openstack':
-      $output = '<h3>' . t('About') . '</h3>';
-      $output .= '<ul>';
-      $output .= '<li>' . \Drupal::service('cloud')->getVersion() . '</li>';
-      $output .= '<li>' . t('This module creates a user interface for managing OpenStack, which is depending on AWS Cloud (aws_cloud) module working with a Drupal service (EC2Service).') . '</li>';
-      $output .= '</ul>';
-      $output .= '<h3>' . t('Features') . '</h3>';
-      $output .= '<ul>';
-      $output .= '<li>' . t('<strong>OpenStack</strong>');
-      $output .= '<ul>';
-      $output .= '<li>' . t('Manage OpenStack.') . '</li>';
-      $output .= '<li>' . t('Support OpenStack launch templates.') . '</li>';
-      $output .= '</ul></li></ul>';
-      $output .= '<p>' . t('For more information, see the <a href=":cloud_documentation">online documentation for the OpenStack module</a>.', [':cloud_documentation' => 'https://www.drupal.org/docs/8/modules/cloud']) . '</p>';
-  }
-  return $output;
+  return \Drupal::service(OpenstackHooks::class)->help($route_name, $route_match);
 }
 
 /**
  * Implements hook_cron().
  */
+#[LegacyHook]
 function openstack_cron(): void {
-  $entities = \Drupal::service('plugin.manager.cloud_config_plugin')->loadConfigEntities('openstack');
-  foreach ($entities ?: [] as $entity) {
-    /** @var \Drupal\openstack\Service\OpenStackServiceInterface $openstack_service */
-    $openstack_service = \Drupal::service('openstack.factory')->get($entity->getCloudContext());
-    $openstack_service->createResourceQueueItems($entity);
-  }
-  \Drupal::service('cloud')->clearAllCacheValues();
+  \Drupal::service(OpenstackHooks::class)->cron();
 }
 
 /**
@@ -140,147 +117,41 @@ function openstack_form_cloud_config_openstack_add_form_create_cloud_context(str
 /**
  * Implements hook_cloud_config_presave().
  */
+#[LegacyHook]
 function openstack_cloud_config_presave(CloudConfig $cloud_config): void {
-  if ($cloud_config->bundle() === 'openstack') {
-    if ($cloud_config->isNew() && !$cloud_config->isRemote()) {
-      // Auto generate the cloud_context.
-      $cloud_context = openstack_form_cloud_config_openstack_add_form_create_cloud_context(
-        $cloud_config->getName() ?? '',
-        $cloud_config->get('field_os_region')->value ?? ''
-      );
-      $cloud_config->set('cloud_context', $cloud_context);
-    }
-    else {
-      $cloud_context = $cloud_config->get('cloud_context')->value;
-    }
-
-    $openstack_ec2_api = $cloud_config->field_use_openstack_ec2_api->value;
-
-    if (!empty($openstack_ec2_api)) {
-      $access_key = $cloud_config->get('field_access_key')->value;
-      $secret_key = $cloud_config->get('field_secret_key')->value;
-
-      if (!empty($cloud_config->get('field_access_key')->value) && !empty($cloud_config->get('field_secret_key')->value)) {
-        // Create credential_file.
-        aws_cloud_create_credential_file($cloud_context, $access_key, $secret_key);
-
-        $cloud_config->set('field_access_key', NULL);
-        $cloud_config->set('field_secret_key', NULL);
-      }
-    }
-  }
+  \Drupal::service(OpenstackHooks::class)->cloudConfigPresave($cloud_config);
 }
 
 /**
  * Implements hook_cloud_config_predelete().
  */
+#[LegacyHook]
 function openstack_cloud_config_predelete(CloudConfig $cloud_config): void {
-  if ($cloud_config->bundle() === 'openstack') {
-    $openstack_ec2_api = $cloud_config->field_use_openstack_ec2_api->value;
-
-    if (!empty($openstack_ec2_api)) {
-
-      $cloud_context = $cloud_config->getCloudContext();
-
-      /** @var \Drupal\openstack\Service\Ec2\OpenStackService $openstack_ec2_service */
-      $openstack_ec2_service = \Drupal::service('openstack.ec2');
-      $openstack_ec2_service->setCloudContext($cloud_context);
-
-      \Drupal::service('cloud')->clearAllEntities('openstack', $cloud_context);
-
-      // Clean up credential files.
-      $credential_file = aws_cloud_ini_file_path($cloud_config->get('cloud_context')->value);
-      \Drupal::service('file_system')->delete($credential_file);
-    }
-    else {
-
-      $cloud_context = $cloud_config->getCloudContext();
-
-      /** @var \Drupal\openstack\Service\Rest\OpenStackService $openstack_rest_service */
-      $openstack_rest_service = \Drupal::service('openstack.rest');
-      $openstack_rest_service->setCloudContext($cloud_context);
-
-      \Drupal::service('cloud')->clearAllEntities('openstack', $cloud_context);
-    }
-    \Drupal::service('cloud')->deleteQueue(\Drupal::service('cloud')->getResourceQueueName($cloud_config));
-  }
+  \Drupal::service(OpenstackHooks::class)->cloudConfigPredelete($cloud_config);
 }
 
 /**
  * Implements hook_cloud_config_delete().
  */
+#[LegacyHook]
 function openstack_cloud_config_delete(CloudConfig $cloud_config): void {
-  if ($cloud_config->bundle() === 'openstack') {
-    // Clear cache cannot happen in hook_cloud_config_predelete().
-    // Moving it into hook_cloud_config_delete().
-    \Drupal::service('cloud')->clearAllCacheValues();
-  }
+  \Drupal::service(OpenstackHooks::class)->cloudConfigDelete($cloud_config);
 }
 
 /**
  * Implements hook_cloud_config_update().
  */
+#[LegacyHook]
 function openstack_cloud_config_update(CloudConfig $cloud_config): void {
-  // Only perform resource update if coming from the cloud service provider
-  // edit form.  This hook can be triggered during user_delete().  user_delete()
-  // runs in progressive batch mode and `openstack_update_resources()` will
-  // try to run a second non-progressive batch mode.  This confuses Drupal
-  // and the original batch processing will never complete, causing the
-  // site to hang.
-  if ($cloud_config->bundle() === 'openstack'
-    && \Drupal::routeMatch()->getRouteName() === 'entity.cloud_config.edit_form') {
-    openstack_update_resources($cloud_config);
-  }
+  \Drupal::service(OpenstackHooks::class)->cloudConfigUpdate($cloud_config);
 }
 
 /**
  * Implements hook_ENTITY_TYPE_predelete().
  */
+#[LegacyHook]
 function openstack_user_predelete($account): void {
-  /** @var Drupal\cloud\Service\CloudService $cloud_service */
-  $cloud_service = \Drupal::service('cloud');
-  $entity_types = $cloud_service->getProviderEntityTypes('openstack');
-  foreach ($entity_types ?: [] as $entity_type) {
-    if (empty($entity_type)) {
-      continue;
-    }
-    $ids = \Drupal::entityTypeManager()
-      ->getStorage($entity_type->id())
-      ->getQuery()
-      ->accessCheck(TRUE)
-      ->condition('uid', $account->id())
-      ->execute();
-    if (count($ids) === 0) {
-      continue;
-    }
-    $cloud_service->reassignUids($ids, [
-      'uid' => 0,
-    ], $entity_type->id(), FALSE, $account, [
-      'openstack_reassign_entity_uid_callback',
-    ]);
-  }
-
-  // Reassign server template entities.
-  $ids = \Drupal::entityTypeManager()
-    ->getStorage('cloud_launch_template')
-    ->getQuery()
-    ->accessCheck(TRUE)
-    ->condition('type', 'openstack')
-    ->condition('uid', $account->id())
-    ->execute();
-
-  if (empty($ids)) {
-    return;
-  }
-
-  // Since cloud_launch_templates have revisions, the array key has the $vid.
-  // The $vid is used to load the entity instead of entity_id.
-  $ids = array_keys($ids);
-  $cloud_service->reassignUids($ids, [
-    'uid' => 0,
-    'revision_uid' => 0,
-  ], 'cloud_launch_template', TRUE, $account, [], 'openstack_reassign_launch_template_uid_callback');
-
+  \Drupal::service(OpenstackHooks::class)->userPredelete($account);
 }
 
 /**
@@ -478,47 +349,17 @@ function openstack_get_id_method(string $entity_type): string {
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function openstack_form_cloud_config_openstack_add_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  $form['#validate'][] = 'openstack_form_cloud_config_openstack_credentials_validate';
-  $form['#validate'][] = 'openstack_form_cloud_config_openstack_cloud_context_validate';
-  $form['#validate'][] = 'openstack_form_cloud_config_openstack_check_iam_permissions_validate';
-
-  // Get Form inputs.
-  $form_inputs = $form_state->getUserInput();
-  $access_key = $form_inputs['field_access_key'][0]['value'] ?? '';
-  $secret_key = $form_inputs['field_secret_key'][0]['value'] ?? '';
-  $openstack_ec2_api = $form_inputs['field_use_openstack_ec2_api']['value'] ?? '';
-
-  // Call region validate function if access key and secret key is not empty.
-  if (!empty($openstack_ec2_api) && !empty($access_key) && !empty($secret_key)) {
-    $form['#validate'][] = 'openstack_form_cloud_config_openstack_region_validate';
-  }
-
-  openstack_form_cloud_config_openstack_form_common_alter($form, $form_state, $form_id);
-
-  $form['cloud_provider']['cloud_context']['#access'] = FALSE;
-  $form['actions']['submit']['#submit'][] = 'openstack_form_cloud_config_openstack_add_form_submit';
+  \Drupal::service(OpenstackHooks::class)->formCloudConfigOpenstackAddFormAlter($form, $form_state, $form_id);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function openstack_form_cloud_config_openstack_edit_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  $form['#validate'][] = 'openstack_form_cloud_config_openstack_credentials_validate';
-  $form['#validate'][] = 'openstack_form_cloud_config_openstack_check_iam_permissions_validate';
-
-  // Get Form inputs.
-  $form_inputs = $form_state->getUserInput();
-  $access_key = $form_inputs['field_access_key'][0]['value'] ?? '';
-  $secret_key = $form_inputs['field_secret_key'][0]['value'] ?? '';
-  $openstack_ec2_api = $form_inputs['field_use_openstack_ec2_api']['value'] ?? '';
-
-  // Call region validate function if access key and secret key is not empty.
-  if (!empty($openstack_ec2_api) && !empty($access_key) && !empty($secret_key)) {
-    $form['#validate'][] = 'openstack_form_cloud_config_openstack_region_validate';
-  }
-
-  openstack_form_cloud_config_openstack_form_common_alter($form, $form_state, $form_id);
+  \Drupal::service(OpenstackHooks::class)->formCloudConfigOpenstackEditFormAlter($form, $form_state, $form_id);
 }
 
 /**
@@ -618,9 +459,9 @@ function openstack_form_cloud_config_openstack_form_common_alter(array &$form, F
 /**
  * Implements hook_default_cloud_config_icon().
  */
+#[LegacyHook]
 function openstack_default_cloud_config_icon(EntityInterface $entity): ?int {
-  // Provides the calling hook with the default OpenStack icon.
-  return \Drupal::service('cloud')->getDefaultCloudConfigIcon($entity, 'openstack');
+  return \Drupal::service(OpenstackHooks::class)->defaultCloudConfigIcon($entity);
 }
 
 /**
@@ -1057,83 +898,33 @@ function openstack_cloud_config_fieldsets(array &$fields): void {
 /**
  * Implements hook_ENTITY_TYPE_view_alter().
  */
+#[LegacyHook]
 function openstack_cloud_config_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display): void {
-  if ($entity->bundle() === 'openstack') {
-    $map_json_url = \Drupal::config('cloud.settings')->get('cloud_custom_location_map_json_url');
-    $url = Url::fromRoute('entity.cloud_config.location', ['cloud_config' => $entity->id()])->toString();
-
-    $build['cloud_config_location_map'] = [
-      '#markup' => '<div id="cloud_config_location"></div>',
-      '#attached' => [
-        'library' => [
-          'cloud/cloud_config_location',
-        ],
-        'drupalSettings' => [
-          'cloud' => [
-            'cloud_location_map_json_url' => $map_json_url,
-            'cloud_config_location_json_url' => $url,
-          ],
-        ],
-      ],
-    ];
-
-    $build['field_location_country']['#access'] = FALSE;
-    $build['field_location_city']['#access'] = FALSE;
-    $build['field_location_longitude']['#access'] = FALSE;
-    $build['field_location_latitude']['#access'] = FALSE;
-
-    openstack_cloud_config_fieldsets($build);
-
-  }
+  \Drupal::service(OpenstackHooks::class)->cloudConfigViewAlter($build, $entity, $display);
 }
 
 /**
  * Implements hook_form_alter().
  */
+#[LegacyHook]
 function openstack_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  if (strpos($form_id, 'views_form_openstack_') === 0) {
-    $form['#submit'][] = 'cloud_views_bulk_form_submit';
-  }
-
-  if ($form['#id'] === 'views-exposed-form-openstack-image-list') {
-    $form['visibility']['#options'][1] = t('Public');
-    $form['visibility']['#options'][0] = t('Private');
-    $form['visibility']['#options'][2] = t('Shared');
-  }
+  \Drupal::service(OpenstackHooks::class)->formAlter($form, $form_state, $form_id);
 }
 
 /**
  * Implements hook_entity_view().
  */
+#[LegacyHook]
 function openstack_entity_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode): void {
-  if (!empty($entity) && $view_mode === 'full'
-  && $entity->getEntityTypeId() === 'openstack_key_pair'
-  && $entity->id() !== NULL) {
-    $keypair = \Drupal::entityTypeManager()->getStorage('openstack_key_pair')->load($entity->id());
-
-    // If the key is on the server, prompt user to download it.
-    if (!empty($keypair) && !empty($keypair->getKeyMaterial())) {
-
-      $url = Url::fromRoute('entity.openstack_key_pair.download', [
-        'cloud_context' => $keypair->getCloudContext(),
-        'key_pair' => $keypair->id(),
-      ])->toString();
-
-      $build['#attached']['drupalSettings']['download_url'] = $url;
-      $build['#attached']['library'][] = 'aws_cloud/aws_cloud_key_pair_download';
-    }
-  }
+  \Drupal::service(OpenstackHooks::class)->entityView($build, $entity, $display, $view_mode);
 }
 
 /**
  * Implements hook_entity_view_alter().
  */
+#[LegacyHook]
 function openstack_entity_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display): void {
-  if ($entity->getEntityTypeId() === 'cloud_launch_template' && $entity->bundle() === 'openstack') {
-    \Drupal::service('cloud')->reorderForm($build, openstack_launch_template_field_orders(FALSE));
-    unset($build['field_instance_type']);
-    $build['#attached']['library'][] = 'aws_cloud/aws_cloud_view_builder';
-  }
+  \Drupal::service(OpenstackHooks::class)->entityViewAlter($build, $entity, $display);
 }
 
 /**
@@ -1709,401 +1500,105 @@ function openstack_get_security_group_options_by_vpc_id($vpc_id): array {
 /**
  * Implements hook_query_TAG_Alter().
  */
+#[LegacyHook]
 function openstack_query_openstack_image_views_access_alter(AlterableInterface $query): void {
-  if (!$account = $query->getMetaData('account')) {
-    $account = \Drupal::currentUser();
-  }
-  if ($account->hasPermission('view any openstack image')) {
-    return;
-  }
-  else {
-    // Add a 'uid' condition and let users view public images.
-    $or = new Condition('OR');
-    $or->condition('openstack_image.uid', $account->id())
-      ->condition('openstack_image.visibility', TRUE);
-    $query->condition($or);
-  }
-
+  \Drupal::service(OpenstackHooks::class)->queryOpenstackImageViewsAccessAlter($query);
 }
 
 /**
  * Implements hook_query_TAG_Alter().
  */
+#[LegacyHook]
 function openstack_query_openstack_key_pair_views_access_alter(AlterableInterface $query): void {
-  if (!$account = $query->getMetaData('account')) {
-    $account = \Drupal::currentUser();
-  }
-  if ($account->hasPermission('view any openstack key pair')) {
-    return;
-  }
-  else {
-    $query->condition('openstack_key_pair.uid', $account->id());
-  }
-
+  \Drupal::service(OpenstackHooks::class)->queryOpenstackKeyPairViewsAccessAlter($query);
 }
 
 /**
  * Implements hook_query_TAG_Alter().
  */
+#[LegacyHook]
 function openstack_query_openstack_security_group_views_access_alter(AlterableInterface $query): void {
-  if (!$account = $query->getMetaData('account')) {
-    $account = \Drupal::currentUser();
-  }
-  if ($account->hasPermission('view any openstack security group')) {
-    return;
-  }
-  else {
-    // Add a 'uid' condition.
-    $query->condition('openstack_security_group.uid', $account->id());
-  }
-
+  \Drupal::service(OpenstackHooks::class)->queryOpenstackSecurityGroupViewsAccessAlter($query);
 }
 
 /**
  * Implements hook_query_TAG_Alter().
  */
+#[LegacyHook]
 function openstack_query_openstack_snapshot_views_access_alter(AlterableInterface $query): void {
-  if (!$account = $query->getMetaData('account')) {
-    $account = \Drupal::currentUser();
-  }
-  if ($account->hasPermission('view any openstack snapshot')) {
-    return;
-  }
-  else {
-    // Add a 'uid' condition.
-    $query->condition('openstack_snapshot.uid', $account->id());
-  }
+  \Drupal::service(OpenstackHooks::class)->queryOpenstackSnapshotViewsAccessAlter($query);
 }
 
 /**
  * Implements hook_query_TAG_Alter().
  */
+#[LegacyHook]
 function openstack_query_openstack_volume_views_access_alter(AlterableInterface $query): void {
-  if (!$account = $query->getMetaData('account')) {
-    $account = \Drupal::currentUser();
-  }
-  if ($account->hasPermission('view any openstack volume')) {
-    return;
-  }
-  else {
-    // Add a 'uid' condition.
-    $query->condition('openstack_volume.uid', $account->id());
-  }
-
+  \Drupal::service(OpenstackHooks::class)->queryOpenstackVolumeViewsAccessAlter($query);
 }
 
 /**
  * Implements hook_query_TAG_Alter().
  */
+#[LegacyHook]
 function openstack_query_openstack_network_views_access_alter(AlterableInterface $query): void {
-  if (!$account = $query->getMetaData('account')) {
-    $account = \Drupal::currentUser();
-  }
-  if ($account->hasPermission('view any openstack network')) {
-    return;
-  }
-
-  // Add a 'uid' condition.
-  $query->condition('openstack_network.uid', $account->id());
+  \Drupal::service(OpenstackHooks::class)->queryOpenstackNetworkViewsAccessAlter($query);
 }
 
 /**
  * Implements hook_query_TAG_Alter().
  */
+#[LegacyHook]
 function openstack_query_openstack_subnet_views_access_alter(AlterableInterface $query): void {
-  if (!$account = $query->getMetaData('account')) {
-    $account = \Drupal::currentUser();
-  }
-  if ($account->hasPermission('view any openstack subnet')) {
-    return;
-  }
-
-  // Add a 'uid' condition.
-  $query->condition('openstack_subnet.uid', $account->id());
+  \Drupal::service(OpenstackHooks::class)->queryOpenstackSubnetViewsAccessAlter($query);
 }
 
 /**
  * Implements hook_query_TAG_Alter().
  */
+#[LegacyHook]
 function openstack_query_openstack_port_views_access_alter(AlterableInterface $query): void {
-  if (!$account = $query->getMetaData('account')) {
-    $account = \Drupal::currentUser();
-  }
-  if ($account->hasPermission('view any openstack port')) {
-    return;
-  }
-
-  // Add a 'uid' condition.
-  $query->condition('openstack_port.uid', $account->id());
+  \Drupal::service(OpenstackHooks::class)->queryOpenstackPortViewsAccessAlter($query);
 }
 
 /**
  * Implements hook_query_TAG_Alter().
  */
+#[LegacyHook]
 function openstack_query_openstack_router_views_access_alter(AlterableInterface $query): void {
-  if (!$account = $query->getMetaData('account')) {
-    $account = \Drupal::currentUser();
-  }
-  if ($account->hasPermission('view any openstack router')) {
-    return;
-  }
-
-  // Add a 'uid' condition.
-  // Because in the view openstack_port.router_interface the alias of
-  // openstack_router will be openstack_router_openstack_port,
-  // we need to use different name for table openstack_router.
-  \Drupal::routeMatch()->getRouteName() === 'view.openstack_port.router_interface'
-  ? $query->condition('openstack_router_openstack_port.uid', $account->id())
-  : $query->condition('openstack_router.uid', $account->id());
+  \Drupal::service(OpenstackHooks::class)->queryOpenstackRouterViewsAccessAlter($query);
 }
 
 /**
  * Implements hook_query_TAG_Alter().
  */
+#[LegacyHook]
 function openstack_query_openstack_stack_views_access_alter(AlterableInterface $query): void {
-  if (!$account = $query->getMetaData('account')) {
-    $account = \Drupal::currentUser();
-  }
-  if ($account->hasPermission('view any openstack stack')) {
-    return;
-  }
-
-  // Add a 'uid' condition.
-  $query->condition('openstack_stack.uid', $account->id());
+  \Drupal::service(OpenstackHooks::class)->queryOpenstackStackViewsAccessAlter($query);
 }
 
 /**
  * Implements hook_entity_operation().
  */
+#[LegacyHook]
 function openstack_entity_operation(EntityInterface $entity): array {
-  $operations = [];
-  $account = \Drupal::currentUser();
-
-  // OpenStack volume.
-  if ($entity->getEntityTypeId() === 'openstack_volume') {
-    if ($account->hasPermission('edit any openstack volume')
-    || ($account->hasPermission('edit own openstack volume')
-    && !empty($entity->getOwner())
-    && $account->id() === $entity->getOwner()->id())) {
-      if ($entity->getState() === 'available') {
-        $operations['attach'] = [
-          'title' => t('Attach'),
-          'url' => $entity->toUrl('attach-form'),
-          'weight' => 20,
-        ];
-      }
-      elseif ($entity->getState() === 'in-use') {
-        $operations['detach'] = [
-          'title' => t('Detach'),
-          'url' => $entity->toUrl('detach-form'),
-          'weight' => 20,
-        ];
-      }
-    }
-
-    if ($account->hasPermission('add openstack snapshot')) {
-      if ($entity->getState() === 'available') {
-        $operations['create_snapshot'] = [
-          'title' => t('Create snapshot'),
-          'url' => Url::fromRoute(
-            'entity.openstack_snapshot.add_form',
-            [
-              'cloud_context' => $entity->getCloudContext(),
-              'volume_id' => $entity->getVolumeId(),
-            ]
-          ),
-          'weight' => 21,
-        ];
-      }
-    }
-    return $operations;
-  }
-
-  // OpenStack floating IP.
-  if ($entity->getEntityTypeId() === 'openstack_floating_ip') {
-    if ($account->hasPermission('edit any openstack floating ip')
-    || ($account->hasPermission('edit own openstack floating ip')
-    && !empty($entity->getOwner())
-    && $account->id() === $entity->getOwner()->id())) {
-      if ($entity->getAssociationId() === NULL) {
-        $operations['associate'] = [
-          'title' => t('Associate'),
-          'url' => $entity->toUrl('associate-form'),
-        ];
-      }
-      else {
-        $operations['disassociate'] = [
-          'title' => t('Disassociate'),
-          'url' => $entity->toUrl('disassociate-form'),
-        ];
-      }
-    }
-    return $operations;
-  }
-
-  // OpenStack snapshot.
-  if ($entity->getEntityTypeId() === 'openstack_snapshot') {
-    if ($account->hasPermission('add openstack volume')) {
-      $operations['create_volume'] = [
-        'title' => t('Create Volume'),
-        'url' => Url::fromRoute(
-          'entity.openstack_volume.add_form',
-          [
-            'cloud_context' => $entity->getCloudContext(),
-            'snapshot_id' => $entity->getSnapshotId(),
-          ]
-        ),
-        'weight' => 20,
-      ];
-    }
-    return $operations;
-  }
-
-  // OpenStack instance.
-  if ($entity->getEntityTypeId() === 'openstack_instance') {
-    if ($account->hasPermission('edit any openstack instance')
-    || ($account->hasPermission('edit own openstack instance')
-    && !empty($entity->getOwner())
-    && $account->id() === $entity->getOwner()->id())) {
-      if ($entity->getInstanceState() === 'running') {
-        $operations['stop'] = [
-          'title' => t('Stop'),
-          'url' => $entity->toUrl('stop-form'),
-          'weight' => 20,
-        ];
-        $operations['reboot'] = [
-          'title' => t('Reboot'),
-          'url' => $entity->toUrl('reboot-form'),
-          'weight' => 21,
-        ];
-        $operations['log'] = [
-          'title' => t('Log'),
-          'url' => $entity->toUrl('console-output-form'),
-          'weight' => 22,
-        ];
-      }
-      elseif ($entity->getInstanceState() === 'stopped') {
-        $operations['start'] = [
-          'title' => t('Start'),
-          'url' => $entity->toUrl('start-form'),
-          'weight' => 20,
-        ];
-        $operations['create_image'] = [
-          'title' => t('Create image'),
-          'url' => $entity->toUrl('create-image-form'),
-          'weight' => 21,
-        ];
-      }
-      $operations['attach-network'] = [
-        'title' => t('Attach interface'),
-        'url' => $entity->toUrl('attach-network-form'),
-        'weight' => 22,
-      ];
-      if (!empty($entity->getPortIds())) {
-        $operations['detach-network'] = [
-          'title' => t('Detach interface'),
-          'url' => $entity->toUrl('detach-network-form'),
-          'weight' => 22,
-        ];
-      }
-    }
-    return $operations;
-  }
-
-  // OpenStack stack.
-  if ($entity->getEntityTypeId() === 'openstack_stack') {
-
-    if ($account->hasPermission('edit any openstack stack')
-    || ($account->hasPermission('edit own openstack stack')
-    && !empty($entity->getOwner())
-    && $account->id() === $entity->getOwner()->id())) {
-
-      $operations['check'] = [
-        'title' => t('Check'),
-        'url' => $entity->toUrl('check-form'),
-        'weight' => 20,
-      ];
-
-      $operations['suspend'] = [
-        'title' => t('Suspend'),
-        'url' => $entity->toUrl('suspend-form'),
-        'weight' => 30,
-      ];
-
-      $operations['resume'] = [
-        'title' => t('Resume'),
-        'url' => $entity->toUrl('resume-form'),
-        'weight' => 40,
-      ];
-    }
-
-    return $operations;
-  }
-
-  // OpenStack user.
-  if ($entity->getEntityTypeId() === 'openstack_user') {
-
-    if ($account->hasPermission('edit openstack user')) {
-
-      $operations['change_password'] = [
-        'title' => t('Change Password'),
-        'url' => $entity->toUrl('change-password-form'),
-        'weight' => 20,
-      ];
-    }
-    return $operations;
-  }
-
-  return $operations;
+  return \Drupal::service(OpenstackHooks::class)->entityOperation($entity);
 }
 
 /**
  * Implements hook_entity_operation_alter().
  */
+#[LegacyHook]
 function openstack_entity_operation_alter(array &$operations, EntityInterface $entity): void {
-
-  if ($entity->getEntityTypeId() === 'openstack_volume') {
-    if ($entity->getState() === 'in-use') {
-      unset($operations['delete']);
-    }
-  }
-
-  if ($entity->getEntityTypeId() === 'openstack_floating_ip') {
-    $association_id = $entity->getAssociationId();
-    if (isset($association_id)) {
-      unset($operations['delete']);
-    }
-  }
-
-  // OpenStack stack.
-  if ($entity->getEntityTypeId() === 'openstack_stack') {
-    if (empty($operations['edit'])) {
-      return;
-    }
-
-    // Remove destination query parameter for edit operation.
-    $url =& $operations['edit']['url'];
-    $options = $url->getOptions();
-    unset($options['query']['destination']);
-    $url->setOptions($options);
-  }
+  \Drupal::service(OpenstackHooks::class)->entityOperationAlter($operations, $entity);
 }
 
 /**
  * Implements hook_query_TAG_Alter().
  */
+#[LegacyHook]
 function openstack_query_openstack_floating_ip_views_access_alter(AlterableInterface $query): void {
-  if (!$account = $query->getMetaData('account')) {
-    $account = \Drupal::currentUser();
-  }
-  if ($account->hasPermission('view any openstack floating ip')) {
-    return;
-  }
-  else {
-    // Add a 'uid' condition.
-    $query->condition('openstack_floating_ip.uid', $account->id());
-  }
-
+  \Drupal::service(OpenstackHooks::class)->queryOpenstackFloatingIpViewsAccessAlter($query);
 }
 
 /**
@@ -2152,93 +1647,41 @@ function openstack_notify_unused_snapshots_owners_and_admin(): void {
 /**
  * Implements hook_query_TAG_Alter().
  */
+#[LegacyHook]
 function openstack_query_openstack_instance_views_access_alter(AlterableInterface $query): void {
-  if (!$account = $query->getMetaData('account')) {
-    $account = \Drupal::currentUser();
-  }
-  if ($account->hasPermission('view any openstack instance')) {
-    return;
-  }
-  else {
-    // Add a 'uid' condition.
-    $query->condition('openstack_instance.uid', $account->id());
-  }
+  \Drupal::service(OpenstackHooks::class)->queryOpenstackInstanceViewsAccessAlter($query);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function openstack_form_cloud_launch_template_openstack_add_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  openstack_form_cloud_launch_template_openstack_form_common_alter($form, $form_state, $form_id);
-
-  $route = \Drupal::routeMatch();
-  $cloud_context = $route->getParameter('cloud_context');
-
-  $openstack_service = \Drupal::service('openstack.factory')->isEc2ServiceType('cloud_config', $cloud_context);
-  if (empty($openstack_service)) {
-    // Overwrite function ::save.
-    $form['actions']['submit']['#submit'][1] = 'openstack_form_cloud_launch_template_openstack_add_form_submit';
-  }
+  \Drupal::service(OpenstackHooks::class)->formCloudLaunchTemplateOpenstackAddFormAlter($form, $form_state, $form_id);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function openstack_form_cloud_launch_template_openstack_edit_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  openstack_form_cloud_launch_template_openstack_form_common_alter($form, $form_state, $form_id);
-
-  // Hide new revision checkbox.
-  $form['new_revision']['#access'] = FALSE;
-
-  // Disable name field.
-  $form['instance']['name']['#disabled'] = TRUE;
-
-  $server_template = $form_state
-    ->getFormObject()
-    ->getEntity();
-  $cloud_context = $server_template->getCloudContext();
-
-  $openstack_service = \Drupal::service('openstack.factory')->isEc2ServiceType('cloud_config', $cloud_context);
-  if (empty($openstack_service)) {
-    // Overwrite function ::save.
-    $form['actions']['submit']['#submit'][1] = 'openstack_form_cloud_launch_template_openstack_edit_form_submit';
-  }
+  \Drupal::service(OpenstackHooks::class)->formCloudLaunchTemplateOpenstackEditFormAlter($form, $form_state, $form_id);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function openstack_form_cloud_launch_template_openstack_copy_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  openstack_form_cloud_launch_template_openstack_form_common_alter($form, $form_state, $form_id);
-
-  // Change name for copy.
-  $name = $form['instance']['name']['widget'][0]['value']['#default_value'];
-  $form['instance']['name']['widget'][0]['value']['#default_value'] = t('copy_of_@name',
-    [
-      '@name' => $name,
-    ]);
-
-  // Hide new revision checkbox.
-  $form['new_revision']['#access'] = FALSE;
-
-  // Clear the revision log message.
-  $form['others']['revision_log_message']['widget'][0]['value']['#default_value'] = NULL;
-
-  // Change value of the submit button.
-  $form['actions']['submit']['#value'] = t('Copy');
-
-  // Delete the delete button.
-  $form['actions']['delete']['#access'] = FALSE;
-
-  // Overwrite function ::save.
-  $form['actions']['submit']['#submit'][1] = 'openstack_form_cloud_launch_template_openstack_copy_form_submit';
+  \Drupal::service(OpenstackHooks::class)->formCloudLaunchTemplateOpenstackCopyFormAlter($form, $form_state, $form_id);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function openstack_form_cloud_launch_template_openstack_delete_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  $form['actions']['submit']['#submit'] = ['openstack_form_cloud_launch_template_openstack_delete_form_submit'];
+  \Drupal::service(OpenstackHooks::class)->formCloudLaunchTemplateOpenstackDeleteFormAlter($form, $form_state, $form_id);
 }
 
 /**
@@ -2270,104 +1713,9 @@ function openstack_form_cloud_launch_template_openstack_delete_form_submit(array
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function openstack_form_cloud_launch_template_openstack_launch_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-
-  $route = \Drupal::routeMatch();
-  $cloud_context = $route->getParameter('cloud_context');
-  /** @var \Drupal\openstack\Service\OpenStackServiceInterface $openstack_service */
-  $openstack_ec2_service = \Drupal::service('openstack.factory')->isEc2ServiceType('cloud_config', $cloud_context);
-
-  $form['cost'] = [
-    '#type' => 'details',
-    '#title' => t('Cost'),
-    '#open' => TRUE,
-  ];
-
-  // Add openstack_flavor.list view.
-  $view = Views::getView('openstack_flavor');
-  $view->setDisplay('list');
-  $pager_options = $view->display_handler->getOption('pager');
-  // Set the number of page links to 'Display all items'.
-  $pager_options['options']['items_per_page'] = 0;
-  // Disable exposing the 'items_per_page' option.
-  $pager_options['options']['expose']['items_per_page'] = FALSE;
-  $pager_options['expose']['items_per_page'] = FALSE;
-  $view->display_handler->setOption('pager', $pager_options);
-  $view->setArguments([$cloud_context]);
-  $form['cost']['flavor'] = $view->executeDisplay();
-
-  $form['#attached']['library'][] = 'openstack/openstack_launch_form';
-
-  if (!empty($openstack_ec2_service)) {
-    $form['automation'] = [
-      '#type' => 'details',
-      '#title' => t('Automation'),
-      '#open' => TRUE,
-    ];
-
-    $form['automation']['description'] = $form['description'];
-    unset($form['description']);
-
-    $form['automation']['termination_protection'] = [
-      '#type' => 'checkbox',
-      '#title' => t('Termination protection'),
-      '#description' => t('Enable the termination protection. If enabled, this instance cannot be terminated using the console, API, or CLI until termination protection is disabled.'),
-      '#default_value' => $form_state->getFormObject()->getEntity()->get('field_termination_protection')->value === '1',
-    ];
-
-    $config = \Drupal::config('openstack.settings');
-    $form['automation']['terminate'] = [
-      '#type' => 'checkbox',
-      '#title' => t('Automatically terminate instance'),
-      '#description' => t('Terminate instance automatically.  Specify termination date in the date picker below.'),
-      '#default_value' => $config->get('openstack_instance_terminate'),
-    ];
-
-    // @todo make 30 days configurable
-    $form['automation']['termination_date'] = [
-      '#type' => 'datetime',
-      '#title' => t('Termination Date'),
-      '#description' => t('The default termination date is 30 days into the future.'),
-      '#default_value' => DrupalDateTime::createFromTimestamp(time() + 2592000),
-    ];
-  }
-  else {
-    $form['automation'] = [
-      '#type' => 'details',
-      '#title' => t('Automation'),
-      '#open' => TRUE,
-    ];
-
-    $form['automation']['description'] = $form['description'];
-    unset($form['description']);
-
-    $form['automation']['termination_protection'] = [
-      '#type' => 'checkbox',
-      '#title' => t('Termination protection'),
-      '#description' => t('Enable the termination protection. If enabled, this instance cannot be terminated until termination protection is disabled.'),
-      '#default_value' => $form_state->getFormObject()->getEntity()->get('field_termination_protection')->value === '1',
-    ];
-  }
-
-  /** @var \Drupal\cloud\Entity\CloudLaunchTemplate $cloud_launch_template */
-  $cloud_launch_template = \Drupal::routeMatch()->getParameter('cloud_launch_template');
-
-  if ($cloud_launch_template->get('field_instance_shutdown_behavior')->value === 'terminate') {
-    // Add a warning message setting a schedule will terminate the instance,
-    // since the shutdown behavior is equal to 'terminate'.
-    $form['automation']['terminate_message'] = [
-      '#markup' => t('Setting a schedule will potentially terminate the instance since the <strong>%text</strong> is set to Terminate',
-        ['%text' => 'Instance shutdown behavior']),
-    ];
-  }
-
-  $view_builder = \Drupal::entityTypeManager()->getViewBuilder('cloud_launch_template');
-  $build = $view_builder->view($cloud_launch_template, 'view');
-  unset($build['#weight']);
-  $build['#pre_render'][] = '\Drupal\aws_cloud\Entity\Ec2\AwsCloudViewBuilder::reorderLaunchTemplate';
-  $form['detail'] = $build;
-
-  $form['#validate'][] = 'openstack_form_cloud_launch_template_openstack_launch_form_validate';
+  \Drupal::service(OpenstackHooks::class)->formCloudLaunchTemplateOpenstackLaunchFormAlter($form, $form_state, $form_id);
 }
 
 /**
@@ -2776,37 +2124,9 @@ function openstack_entity_type_alter(array &$entity_types): void {
 /**
  * Implements hook_preprocess_menu_local_tasks().
  */
+#[LegacyHook]
 function openstack_preprocess_menu_local_tasks(&$variables) {
-  // Hide the Revision tab from OpenStack cloud configs pages.
-  $routes = [
-    'entity.cloud_launch_template.canonical',
-    'entity.cloud_launch_template.edit_form',
-    'entity.cloud_launch_template.delete_form',
-    'entity.cloud_launch_template.launch',
-    'entity.cloud_launch_template.copy',
-  ];
-
-  $current_route = \Drupal::routeMatch();
-  if (in_array($current_route->getRouteName(), $routes, FALSE) === FALSE) {
-    return;
-  }
-
-  $cloud_context = $current_route->getParameter('cloud_context');
-  if (empty($cloud_context)) {
-    return;
-  }
-
-  /** @var \Drupal\cloud\Plugin\cloud\CloudPluginManager $cloud_plugin_manager */
-  $cloud_config_plugin = \Drupal::service('plugin.manager.cloud_config_plugin');
-  $cloud_config_plugin->setCloudContext($cloud_context);
-  $cloud_config = $cloud_config_plugin->loadConfigEntity();
-  if (empty($cloud_config)) {
-    return;
-  }
-  if ($cloud_config->bundle() === 'openstack') {
-    unset($variables['primary']['entity.cloud_launch_template.version_history']);
-  }
-
+  \Drupal::service(OpenstackHooks::class)->preprocessMenuLocalTasks($variables);
 }
 
 /**
@@ -2837,35 +2157,15 @@ function openstack_file_validate_yaml(FileInterface $file): array {
 /**
  * Implements hook_mail_alter().
  */
+#[LegacyHook]
 function openstack_mail_alter(&$message) {
-  if ($message['key'] === 'quotas_request') {
-    $message['subject'] = $message['params']['subject'];
-    $message['body'][] = $message['params']['message'];
-
-    // HTML email.
-    $message['headers']['Content-Type'] = 'text/html';
-    $message['headers']['MIME-Version'] = '1.0';
-  }
-  elseif ($message['key'] === 'quotas_approved') {
-    $message['subject'] = $message['params']['subject'];
-    $message['body'][] = $message['params']['message'];
-  }
+  \Drupal::service(OpenstackHooks::class)->mailAlter($message);
 }
 
 /**
  * Implements hook_theme().
  */
+#[LegacyHook]
 function openstack_theme($existing, $type, $theme, $path) {
-  return [
-    'usage_pie_chart' => [
-      'template' => 'usage_pie_chart',
-      'variables' => [
-        'title' => NULL,
-        'used' => NULL,
-        'total' => NULL,
-        'unit' => NULL,
-        'description_template' => t('Used %d%s of %d%s'),
-      ],
-    ],
-  ];
+  return \Drupal::service(OpenstackHooks::class)->theme($existing, $type, $theme, $path);
 }
diff --git a/modules/cloud_service_providers/openstack/openstack.services.yml b/modules/cloud_service_providers/openstack/openstack.services.yml
index e468877..2b4b52d 100644
--- a/modules/cloud_service_providers/openstack/openstack.services.yml
+++ b/modules/cloud_service_providers/openstack/openstack.services.yml
@@ -25,3 +25,11 @@ services:
   openstack.operations:
     class: Drupal\openstack\Service\OpenStackOperationsService
     arguments: ['@aws_cloud.openstack.operations', '@plugin.manager.cloud_config_plugin', '@cloud', '@openstack.ec2', '@entity_type.manager', '@messenger', '@openstack.factory', '@request_stack', '@current_route_match', '@entity.link_renderer', '@datetime.time', '@module_handler', '@queue', '@current_user', '@config.factory']
+
+  Drupal\openstack\Hook\OpenstackHooks:
+    class: Drupal\openstack\Hook\OpenstackHooks
+    autowire: true
+
+  Drupal\openstack\Hook\OpenstackTokensHooks:
+    class: Drupal\openstack\Hook\OpenstackTokensHooks
+    autowire: true
diff --git a/modules/cloud_service_providers/openstack/openstack.tokens.inc b/modules/cloud_service_providers/openstack/openstack.tokens.inc
index 0050d7f..e18752a 100644
--- a/modules/cloud_service_providers/openstack/openstack.tokens.inc
+++ b/modules/cloud_service_providers/openstack/openstack.tokens.inc
@@ -5,109 +5,24 @@
  * Builds placeholder replacement tokens for openstack-related data.
  */
 
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\openstack\Hook\OpenstackTokensHooks;
 use Drupal\Core\Render\BubbleableMetadata;
 
 /**
  * Implements hook_token_info().
  */
+#[LegacyHook]
 function openstack_token_info(): array {
-
-  $types['openstack_quota'] = [
-    'name' => t('OpenStack quota'),
-    'description' => t('Tokens related to individual OpenStack quota.'),
-    'needs-data' => 'openstack_quota',
-  ];
-  $quota['name'] = [
-    'name' => t('Quota name'),
-    'description' => t('Enter the name of the quota entity.'),
-  ];
-  $quota['quota_link'] = [
-    'name' => t('Quota link'),
-    'description' => t('An absolute link to quota.'),
-  ];
-  $quota['quota_edit_link'] = [
-    'name' => t('Quota edit link'),
-    'description' => t('An absolute link to edit the quota.'),
-  ];
-  $quota['changed'] = [
-    'name' => t('Change date'),
-    'description' => t('The quota change date.'),
-  ];
-
-  $types['openstack_quota_request'] = [
-    'name' => t('OpenStack quota request'),
-    'description' => t('Tokens related to individual OpenStack quota request.'),
-    'needs-data' => 'openstack_quota_request',
-  ];
-  $quota_request['name'] = [
-    'name' => t('Quota name'),
-    'description' => t('Enter the name of the quota entity.'),
-  ];
-  $quota_request['quota_link'] = [
-    'name' => t('Quota link'),
-    'description' => t('An absolute link to quota.'),
-  ];
-  $quota_request['quota_edit_link'] = [
-    'name' => t('Quota edit link'),
-    'description' => t('An absolute link to edit the quota.'),
-  ];
-  $quota_request['changed'] = [
-    'name' => t('Change date'),
-    'description' => t('The quota change date.'),
-  ];
-  $quota_request['quota_button_approve'] = [
-    'name' => t('Approve button'),
-    'description' => t('The quota approve button.'),
-  ];
-
-  $types['openstack_quota_email'] = [
-    'name' => t('OpenStack quota email'),
-    'description' => t('Tokens related to individual OpenStack quota email.'),
-    'needs-data' => 'openstack_quota_email',
-  ];
-  $quota_email['quotas'] = [
-    'name' => t('List of quotas'),
-    'description' => t('List of quotas to display to a user.'),
-  ];
-
-  $types['openstack_quota_request_email'] = [
-    'name' => t('OpenStack quota request email'),
-    'description' => t('Tokens related to individual OpenStack quota request email.'),
-    'needs-data' => 'openstack_quota_request_email',
-  ];
-  $openstack_request_email['quotas_request'] = [
-    'name' => t('List of request quotas'),
-    'description' => t('List of request quotas to display to a user.'),
-  ];
-  return [
-    'types' => $types,
-    'tokens' => [
-      'openstack_quota' => $quota,
-      'openstack_quota_request' => $quota_request,
-      'openstack_quota_email' => $quota_email,
-      'openstack_quota_request_email' => $openstack_request_email,
-    ],
-  ];
+  return \Drupal::service(OpenstackTokensHooks::class)->tokenInfo();
 }
 
 /**
  * Implements hook_tokens().
  */
+#[LegacyHook]
 function openstack_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata): array {
-  $replacements = [];
-  if ($type === 'openstack_quota' && !empty($data['openstack_quota'])) {
-    $replacements = openstack_quota_tokens($tokens, $data);
-  }
-  elseif ($type === 'openstack_quota_request' && !empty($data['openstack_quota_request'])) {
-    $replacements = openstack_quota_request_tokens($tokens, $data);
-  }
-  elseif ($type === 'openstack_quota_email') {
-    $replacements = openstack_quota_email_tokens($tokens, $data);
-  }
-  elseif ($type === 'openstack_quota_request_email') {
-    $replacements = openstack_quota_request_email_tokens($tokens, $data);
-  }
-  return $replacements;
+  return \Drupal::service(OpenstackTokensHooks::class)->tokens($type, $tokens, $data, $options, $bubbleable_metadata);
 }
 
 /**
diff --git a/modules/cloud_service_providers/openstack/src/Hook/OpenstackHooks.php b/modules/cloud_service_providers/openstack/src/Hook/OpenstackHooks.php
new file mode 100644
index 0000000..4015b28
--- /dev/null
+++ b/modules/cloud_service_providers/openstack/src/Hook/OpenstackHooks.php
@@ -0,0 +1,922 @@
+<?php
+
+namespace Drupal\openstack\Hook;
+
+use Drupal\Core\Datetime\DrupalDateTime;
+use Drupal\views\Views;
+use Drupal\Core\Database\Query\Condition;
+use Drupal\Core\Database\Query\AlterableInterface;
+use Drupal\Core\Url;
+use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\cloud\Entity\CloudConfig;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for openstack.
+ */
+class OpenstackHooks {
+  use StringTranslationTrait;
+
+  /**
+   * Implements hook_help().
+   */
+  #[Hook('help')]
+  public function help($route_name, RouteMatchInterface $route_match): string {
+    $output = '';
+    switch ($route_name) {
+      case 'help.page.openstack':
+        $output = '<h3>' . $this->t('About') . '</h3>';
+        $output .= '<ul>';
+        $output .= '<li>' . \Drupal::service('cloud')->getVersion() . '</li>';
+        $output .= '<li>' . $this->t('This module creates a user interface for managing OpenStack, which is depending on AWS Cloud (aws_cloud) module working with a Drupal service (EC2Service).') . '</li>';
+        $output .= '</ul>';
+        $output .= '<h3>' . $this->t('Features') . '</h3>';
+        $output .= '<ul>';
+        $output .= '<li>' . $this->t('<strong>OpenStack</strong>');
+        $output .= '<ul>';
+        $output .= '<li>' . $this->t('Manage OpenStack.') . '</li>';
+        $output .= '<li>' . $this->t('Support OpenStack launch templates.') . '</li>';
+        $output .= '</ul></li></ul>';
+        $output .= '<p>' . $this->t('For more information, see the <a href=":cloud_documentation">online documentation for the OpenStack module</a>.', [
+          ':cloud_documentation' => 'https://www.drupal.org/docs/8/modules/cloud',
+        ]) . '</p>';
+    }
+    return $output;
+  }
+
+  /**
+   * Implements hook_cron().
+   */
+  #[Hook('cron')]
+  public static function cron(): void {
+    $entities = \Drupal::service('plugin.manager.cloud_config_plugin')->loadConfigEntities('openstack');
+    foreach ($entities ?: [] as $entity) {
+      /** @var \Drupal\openstack\Service\OpenStackServiceInterface $openstack_service */
+      $openstack_service = \Drupal::service('openstack.factory')->get($entity->getCloudContext());
+      $openstack_service->createResourceQueueItems($entity);
+    }
+    \Drupal::service('cloud')->clearAllCacheValues();
+  }
+
+  /**
+   * Implements hook_cloud_config_presave().
+   */
+  #[Hook('cloud_config_presave')]
+  public static function cloudConfigPresave(CloudConfig $cloud_config): void {
+    if ($cloud_config->bundle() === 'openstack') {
+      if ($cloud_config->isNew() && !$cloud_config->isRemote()) {
+        // Auto generate the cloud_context.
+        $cloud_context = openstack_form_cloud_config_openstack_add_form_create_cloud_context($cloud_config->getName() ?? '', $cloud_config->get('field_os_region')->value ?? '');
+        $cloud_config->set('cloud_context', $cloud_context);
+      }
+      else {
+        $cloud_context = $cloud_config->get('cloud_context')->value;
+      }
+      $openstack_ec2_api = $cloud_config->field_use_openstack_ec2_api->value;
+      if (!empty($openstack_ec2_api)) {
+        $access_key = $cloud_config->get('field_access_key')->value;
+        $secret_key = $cloud_config->get('field_secret_key')->value;
+        if (!empty($cloud_config->get('field_access_key')->value) && !empty($cloud_config->get('field_secret_key')->value)) {
+          // Create credential_file.
+          aws_cloud_create_credential_file($cloud_context, $access_key, $secret_key);
+          $cloud_config->set('field_access_key', NULL);
+          $cloud_config->set('field_secret_key', NULL);
+        }
+      }
+    }
+  }
+
+  /**
+   * Implements hook_cloud_config_predelete().
+   */
+  #[Hook('cloud_config_predelete')]
+  public static function cloudConfigPredelete(CloudConfig $cloud_config): void {
+    if ($cloud_config->bundle() === 'openstack') {
+      $openstack_ec2_api = $cloud_config->field_use_openstack_ec2_api->value;
+      if (!empty($openstack_ec2_api)) {
+        $cloud_context = $cloud_config->getCloudContext();
+        /** @var \Drupal\openstack\Service\Ec2\OpenStackService $openstack_ec2_service */
+        $openstack_ec2_service = \Drupal::service('openstack.ec2');
+        $openstack_ec2_service->setCloudContext($cloud_context);
+        \Drupal::service('cloud')->clearAllEntities('openstack', $cloud_context);
+        // Clean up credential files.
+        $credential_file = aws_cloud_ini_file_path($cloud_config->get('cloud_context')->value);
+        \Drupal::service('file_system')->delete($credential_file);
+      }
+      else {
+        $cloud_context = $cloud_config->getCloudContext();
+        /** @var \Drupal\openstack\Service\Rest\OpenStackService $openstack_rest_service */
+        $openstack_rest_service = \Drupal::service('openstack.rest');
+        $openstack_rest_service->setCloudContext($cloud_context);
+        \Drupal::service('cloud')->clearAllEntities('openstack', $cloud_context);
+      }
+      \Drupal::service('cloud')->deleteQueue(\Drupal::service('cloud')->getResourceQueueName($cloud_config));
+    }
+  }
+
+  /**
+   * Implements hook_cloud_config_delete().
+   */
+  #[Hook('cloud_config_delete')]
+  public static function cloudConfigDelete(CloudConfig $cloud_config): void {
+    if ($cloud_config->bundle() === 'openstack') {
+      // Clear cache cannot happen in hook_cloud_config_predelete().
+      // Moving it into hook_cloud_config_delete().
+      \Drupal::service('cloud')->clearAllCacheValues();
+    }
+  }
+
+  /**
+   * Implements hook_cloud_config_update().
+   */
+  #[Hook('cloud_config_update')]
+  public static function cloudConfigUpdate(CloudConfig $cloud_config): void {
+    // Only perform resource update if coming from the cloud service provider
+    // edit form.  This hook can be triggered during user_delete().  user_delete()
+    // runs in progressive batch mode and `openstack_update_resources()` will
+    // try to run a second non-progressive batch mode.  This confuses Drupal
+    // and the original batch processing will never complete, causing the
+    // site to hang.
+    if ($cloud_config->bundle() === 'openstack' && \Drupal::routeMatch()->getRouteName() === 'entity.cloud_config.edit_form') {
+      openstack_update_resources($cloud_config);
+    }
+  }
+
+  /**
+   * Implements hook_ENTITY_TYPE_predelete().
+   */
+  #[Hook('user_predelete')]
+  public static function userPredelete($account): void {
+    /** @var Drupal\cloud\Service\CloudService $cloud_service */
+    $cloud_service = \Drupal::service('cloud');
+    $entity_types = $cloud_service->getProviderEntityTypes('openstack');
+    foreach ($entity_types ?: [] as $entity_type) {
+      if (empty($entity_type)) {
+        continue;
+      }
+      $ids = \Drupal::entityTypeManager()->getStorage($entity_type->id())->getQuery()->accessCheck(TRUE)->condition('uid', $account->id())->execute();
+      if (count($ids) === 0) {
+        continue;
+      }
+      $cloud_service->reassignUids($ids, [
+        'uid' => 0,
+      ], $entity_type->id(), FALSE, $account, [
+        'openstack_reassign_entity_uid_callback',
+      ]);
+    }
+    // Reassign server template entities.
+    $ids = \Drupal::entityTypeManager()->getStorage('cloud_launch_template')->getQuery()->accessCheck(TRUE)->condition('type', 'openstack')->condition('uid', $account->id())->execute();
+    if (empty($ids)) {
+      return;
+    }
+    // Since cloud_launch_templates have revisions, the array key has the $vid.
+    // The $vid is used to load the entity instead of entity_id.
+    $ids = array_keys($ids);
+    $cloud_service->reassignUids($ids, [
+      'uid' => 0,
+      'revision_uid' => 0,
+    ], 'cloud_launch_template', TRUE, $account, [], 'openstack_reassign_launch_template_uid_callback');
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_config_openstack_add_form_alter')]
+  public static function formCloudConfigOpenstackAddFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    $form['#validate'][] = 'openstack_form_cloud_config_openstack_credentials_validate';
+    $form['#validate'][] = 'openstack_form_cloud_config_openstack_cloud_context_validate';
+    $form['#validate'][] = 'openstack_form_cloud_config_openstack_check_iam_permissions_validate';
+    // Get Form inputs.
+    $form_inputs = $form_state->getUserInput();
+    $access_key = $form_inputs['field_access_key'][0]['value'] ?? '';
+    $secret_key = $form_inputs['field_secret_key'][0]['value'] ?? '';
+    $openstack_ec2_api = $form_inputs['field_use_openstack_ec2_api']['value'] ?? '';
+    // Call region validate function if access key and secret key is not empty.
+    if (!empty($openstack_ec2_api) && !empty($access_key) && !empty($secret_key)) {
+      $form['#validate'][] = 'openstack_form_cloud_config_openstack_region_validate';
+    }
+    openstack_form_cloud_config_openstack_form_common_alter($form, $form_state, $form_id);
+    $form['cloud_provider']['cloud_context']['#access'] = FALSE;
+    $form['actions']['submit']['#submit'][] = 'openstack_form_cloud_config_openstack_add_form_submit';
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_config_openstack_edit_form_alter')]
+  public static function formCloudConfigOpenstackEditFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    $form['#validate'][] = 'openstack_form_cloud_config_openstack_credentials_validate';
+    $form['#validate'][] = 'openstack_form_cloud_config_openstack_check_iam_permissions_validate';
+    // Get Form inputs.
+    $form_inputs = $form_state->getUserInput();
+    $access_key = $form_inputs['field_access_key'][0]['value'] ?? '';
+    $secret_key = $form_inputs['field_secret_key'][0]['value'] ?? '';
+    $openstack_ec2_api = $form_inputs['field_use_openstack_ec2_api']['value'] ?? '';
+    // Call region validate function if access key and secret key is not empty.
+    if (!empty($openstack_ec2_api) && !empty($access_key) && !empty($secret_key)) {
+      $form['#validate'][] = 'openstack_form_cloud_config_openstack_region_validate';
+    }
+    openstack_form_cloud_config_openstack_form_common_alter($form, $form_state, $form_id);
+  }
+
+  /**
+   * Implements hook_default_cloud_config_icon().
+   */
+  #[Hook('default_cloud_config_icon')]
+  public static function defaultCloudConfigIcon(EntityInterface $entity): ?int {
+    // Provides the calling hook with the default OpenStack icon.
+    return \Drupal::service('cloud')->getDefaultCloudConfigIcon($entity, 'openstack');
+  }
+
+  /**
+   * Implements hook_ENTITY_TYPE_view_alter().
+   */
+  #[Hook('cloud_config_view_alter')]
+  public static function cloudConfigViewAlter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display): void {
+    if ($entity->bundle() === 'openstack') {
+      $map_json_url = \Drupal::config('cloud.settings')->get('cloud_custom_location_map_json_url');
+      $url = Url::fromRoute('entity.cloud_config.location', [
+        'cloud_config' => $entity->id(),
+      ])->toString();
+      $build['cloud_config_location_map'] = [
+        '#markup' => '<div id="cloud_config_location"></div>',
+        '#attached' => [
+          'library' => [
+            'cloud/cloud_config_location',
+          ],
+          'drupalSettings' => [
+            'cloud' => [
+              'cloud_location_map_json_url' => $map_json_url,
+              'cloud_config_location_json_url' => $url,
+            ],
+          ],
+        ],
+      ];
+      $build['field_location_country']['#access'] = FALSE;
+      $build['field_location_city']['#access'] = FALSE;
+      $build['field_location_longitude']['#access'] = FALSE;
+      $build['field_location_latitude']['#access'] = FALSE;
+      openstack_cloud_config_fieldsets($build);
+    }
+  }
+
+  /**
+   * Implements hook_form_alter().
+   */
+  #[Hook('form_alter')]
+  public function formAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    if (strpos($form_id, 'views_form_openstack_') === 0) {
+      $form['#submit'][] = 'cloud_views_bulk_form_submit';
+    }
+    if ($form['#id'] === 'views-exposed-form-openstack-image-list') {
+      $form['visibility']['#options'][1] = $this->t('Public');
+      $form['visibility']['#options'][0] = $this->t('Private');
+      $form['visibility']['#options'][2] = $this->t('Shared');
+    }
+  }
+
+  /**
+   * Implements hook_entity_view().
+   */
+  #[Hook('entity_view')]
+  public static function entityView(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode): void {
+    if (!empty($entity) && $view_mode === 'full' && $entity->getEntityTypeId() === 'openstack_key_pair' && $entity->id() !== NULL) {
+      $keypair = \Drupal::entityTypeManager()->getStorage('openstack_key_pair')->load($entity->id());
+      // If the key is on the server, prompt user to download it.
+      if (!empty($keypair) && !empty($keypair->getKeyMaterial())) {
+        $url = Url::fromRoute('entity.openstack_key_pair.download', [
+          'cloud_context' => $keypair->getCloudContext(),
+          'key_pair' => $keypair->id(),
+        ])->toString();
+        $build['#attached']['drupalSettings']['download_url'] = $url;
+        $build['#attached']['library'][] = 'aws_cloud/aws_cloud_key_pair_download';
+      }
+    }
+  }
+
+  /**
+   * Implements hook_entity_view_alter().
+   */
+  #[Hook('entity_view_alter')]
+  public static function entityViewAlter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display): void {
+    if ($entity->getEntityTypeId() === 'cloud_launch_template' && $entity->bundle() === 'openstack') {
+      \Drupal::service('cloud')->reorderForm($build, openstack_launch_template_field_orders(FALSE));
+      unset($build['field_instance_type']);
+      $build['#attached']['library'][] = 'aws_cloud/aws_cloud_view_builder';
+    }
+  }
+
+  /**
+   * Implements hook_query_TAG_Alter().
+   */
+  #[Hook('query_openstack_image_views_access_alter')]
+  public static function queryOpenstackImageViewsAccessAlter(AlterableInterface $query): void {
+    if (!$account = $query->getMetaData('account')) {
+      $account = \Drupal::currentUser();
+    }
+    if ($account->hasPermission('view any openstack image')) {
+      return;
+    }
+    else {
+      // Add a 'uid' condition and let users view public images.
+      $or = new Condition('OR');
+      $or->condition('openstack_image.uid', $account->id())->condition('openstack_image.visibility', TRUE);
+      $query->condition($or);
+    }
+  }
+
+  /**
+   * Implements hook_query_TAG_Alter().
+   */
+  #[Hook('query_openstack_key_pair_views_access_alter')]
+  public static function queryOpenstackKeyPairViewsAccessAlter(AlterableInterface $query): void {
+    if (!$account = $query->getMetaData('account')) {
+      $account = \Drupal::currentUser();
+    }
+    if ($account->hasPermission('view any openstack key pair')) {
+      return;
+    }
+    else {
+      $query->condition('openstack_key_pair.uid', $account->id());
+    }
+  }
+
+  /**
+   * Implements hook_query_TAG_Alter().
+   */
+  #[Hook('query_openstack_security_group_views_access_alter')]
+  public static function queryOpenstackSecurityGroupViewsAccessAlter(AlterableInterface $query): void {
+    if (!$account = $query->getMetaData('account')) {
+      $account = \Drupal::currentUser();
+    }
+    if ($account->hasPermission('view any openstack security group')) {
+      return;
+    }
+    else {
+      // Add a 'uid' condition.
+      $query->condition('openstack_security_group.uid', $account->id());
+    }
+  }
+
+  /**
+   * Implements hook_query_TAG_Alter().
+   */
+  #[Hook('query_openstack_snapshot_views_access_alter')]
+  public static function queryOpenstackSnapshotViewsAccessAlter(AlterableInterface $query): void {
+    if (!$account = $query->getMetaData('account')) {
+      $account = \Drupal::currentUser();
+    }
+    if ($account->hasPermission('view any openstack snapshot')) {
+      return;
+    }
+    else {
+      // Add a 'uid' condition.
+      $query->condition('openstack_snapshot.uid', $account->id());
+    }
+  }
+
+  /**
+   * Implements hook_query_TAG_Alter().
+   */
+  #[Hook('query_openstack_volume_views_access_alter')]
+  public static function queryOpenstackVolumeViewsAccessAlter(AlterableInterface $query): void {
+    if (!$account = $query->getMetaData('account')) {
+      $account = \Drupal::currentUser();
+    }
+    if ($account->hasPermission('view any openstack volume')) {
+      return;
+    }
+    else {
+      // Add a 'uid' condition.
+      $query->condition('openstack_volume.uid', $account->id());
+    }
+  }
+
+  /**
+   * Implements hook_query_TAG_Alter().
+   */
+  #[Hook('query_openstack_network_views_access_alter')]
+  public static function queryOpenstackNetworkViewsAccessAlter(AlterableInterface $query): void {
+    if (!$account = $query->getMetaData('account')) {
+      $account = \Drupal::currentUser();
+    }
+    if ($account->hasPermission('view any openstack network')) {
+      return;
+    }
+    // Add a 'uid' condition.
+    $query->condition('openstack_network.uid', $account->id());
+  }
+
+  /**
+   * Implements hook_query_TAG_Alter().
+   */
+  #[Hook('query_openstack_subnet_views_access_alter')]
+  public static function queryOpenstackSubnetViewsAccessAlter(AlterableInterface $query): void {
+    if (!$account = $query->getMetaData('account')) {
+      $account = \Drupal::currentUser();
+    }
+    if ($account->hasPermission('view any openstack subnet')) {
+      return;
+    }
+    // Add a 'uid' condition.
+    $query->condition('openstack_subnet.uid', $account->id());
+  }
+
+  /**
+   * Implements hook_query_TAG_Alter().
+   */
+  #[Hook('query_openstack_port_views_access_alter')]
+  public static function queryOpenstackPortViewsAccessAlter(AlterableInterface $query): void {
+    if (!$account = $query->getMetaData('account')) {
+      $account = \Drupal::currentUser();
+    }
+    if ($account->hasPermission('view any openstack port')) {
+      return;
+    }
+    // Add a 'uid' condition.
+    $query->condition('openstack_port.uid', $account->id());
+  }
+
+  /**
+   * Implements hook_query_TAG_Alter().
+   */
+  #[Hook('query_openstack_router_views_access_alter')]
+  public static function queryOpenstackRouterViewsAccessAlter(AlterableInterface $query): void {
+    if (!$account = $query->getMetaData('account')) {
+      $account = \Drupal::currentUser();
+    }
+    if ($account->hasPermission('view any openstack router')) {
+      return;
+    }
+    // Add a 'uid' condition.
+    // Because in the view openstack_port.router_interface the alias of
+    // openstack_router will be openstack_router_openstack_port,
+    // we need to use different name for table openstack_router.
+    \Drupal::routeMatch()->getRouteName() === 'view.openstack_port.router_interface' ? $query->condition('openstack_router_openstack_port.uid', $account->id()) : $query->condition('openstack_router.uid', $account->id());
+  }
+
+  /**
+   * Implements hook_query_TAG_Alter().
+   */
+  #[Hook('query_openstack_stack_views_access_alter')]
+  public static function queryOpenstackStackViewsAccessAlter(AlterableInterface $query): void {
+    if (!$account = $query->getMetaData('account')) {
+      $account = \Drupal::currentUser();
+    }
+    if ($account->hasPermission('view any openstack stack')) {
+      return;
+    }
+    // Add a 'uid' condition.
+    $query->condition('openstack_stack.uid', $account->id());
+  }
+
+  /**
+   * Implements hook_entity_operation().
+   */
+  #[Hook('entity_operation')]
+  public function entityOperation(EntityInterface $entity): array {
+    $operations = [];
+    $account = \Drupal::currentUser();
+    // OpenStack volume.
+    if ($entity->getEntityTypeId() === 'openstack_volume') {
+      if ($account->hasPermission('edit any openstack volume') || $account->hasPermission('edit own openstack volume') && !empty($entity->getOwner()) && $account->id() === $entity->getOwner()->id()) {
+        if ($entity->getState() === 'available') {
+          $operations['attach'] = [
+            'title' => $this->t('Attach'),
+            'url' => $entity->toUrl('attach-form'),
+            'weight' => 20,
+          ];
+        }
+        elseif ($entity->getState() === 'in-use') {
+          $operations['detach'] = [
+            'title' => $this->t('Detach'),
+            'url' => $entity->toUrl('detach-form'),
+            'weight' => 20,
+          ];
+        }
+      }
+      if ($account->hasPermission('add openstack snapshot')) {
+        if ($entity->getState() === 'available') {
+          $operations['create_snapshot'] = [
+            'title' => $this->t('Create snapshot'),
+            'url' => Url::fromRoute('entity.openstack_snapshot.add_form', [
+              'cloud_context' => $entity->getCloudContext(),
+              'volume_id' => $entity->getVolumeId(),
+            ]),
+            'weight' => 21,
+          ];
+        }
+      }
+      return $operations;
+    }
+    // OpenStack floating IP.
+    if ($entity->getEntityTypeId() === 'openstack_floating_ip') {
+      if ($account->hasPermission('edit any openstack floating ip') || $account->hasPermission('edit own openstack floating ip') && !empty($entity->getOwner()) && $account->id() === $entity->getOwner()->id()) {
+        if ($entity->getAssociationId() === NULL) {
+          $operations['associate'] = [
+            'title' => $this->t('Associate'),
+            'url' => $entity->toUrl('associate-form'),
+          ];
+        }
+        else {
+          $operations['disassociate'] = [
+            'title' => $this->t('Disassociate'),
+            'url' => $entity->toUrl('disassociate-form'),
+          ];
+        }
+      }
+      return $operations;
+    }
+    // OpenStack snapshot.
+    if ($entity->getEntityTypeId() === 'openstack_snapshot') {
+      if ($account->hasPermission('add openstack volume')) {
+        $operations['create_volume'] = [
+          'title' => $this->t('Create Volume'),
+          'url' => Url::fromRoute('entity.openstack_volume.add_form', [
+            'cloud_context' => $entity->getCloudContext(),
+            'snapshot_id' => $entity->getSnapshotId(),
+          ]),
+          'weight' => 20,
+        ];
+      }
+      return $operations;
+    }
+    // OpenStack instance.
+    if ($entity->getEntityTypeId() === 'openstack_instance') {
+      if ($account->hasPermission('edit any openstack instance') || $account->hasPermission('edit own openstack instance') && !empty($entity->getOwner()) && $account->id() === $entity->getOwner()->id()) {
+        if ($entity->getInstanceState() === 'running') {
+          $operations['stop'] = [
+            'title' => $this->t('Stop'),
+            'url' => $entity->toUrl('stop-form'),
+            'weight' => 20,
+          ];
+          $operations['reboot'] = [
+            'title' => $this->t('Reboot'),
+            'url' => $entity->toUrl('reboot-form'),
+            'weight' => 21,
+          ];
+          $operations['log'] = [
+            'title' => $this->t('Log'),
+            'url' => $entity->toUrl('console-output-form'),
+            'weight' => 22,
+          ];
+        }
+        elseif ($entity->getInstanceState() === 'stopped') {
+          $operations['start'] = [
+            'title' => $this->t('Start'),
+            'url' => $entity->toUrl('start-form'),
+            'weight' => 20,
+          ];
+          $operations['create_image'] = [
+            'title' => $this->t('Create image'),
+            'url' => $entity->toUrl('create-image-form'),
+            'weight' => 21,
+          ];
+        }
+        $operations['attach-network'] = [
+          'title' => $this->t('Attach interface'),
+          'url' => $entity->toUrl('attach-network-form'),
+          'weight' => 22,
+        ];
+        if (!empty($entity->getPortIds())) {
+          $operations['detach-network'] = [
+            'title' => $this->t('Detach interface'),
+            'url' => $entity->toUrl('detach-network-form'),
+            'weight' => 22,
+          ];
+        }
+      }
+      return $operations;
+    }
+    // OpenStack stack.
+    if ($entity->getEntityTypeId() === 'openstack_stack') {
+      if ($account->hasPermission('edit any openstack stack') || $account->hasPermission('edit own openstack stack') && !empty($entity->getOwner()) && $account->id() === $entity->getOwner()->id()) {
+        $operations['check'] = [
+          'title' => $this->t('Check'),
+          'url' => $entity->toUrl('check-form'),
+          'weight' => 20,
+        ];
+        $operations['suspend'] = [
+          'title' => $this->t('Suspend'),
+          'url' => $entity->toUrl('suspend-form'),
+          'weight' => 30,
+        ];
+        $operations['resume'] = [
+          'title' => $this->t('Resume'),
+          'url' => $entity->toUrl('resume-form'),
+          'weight' => 40,
+        ];
+      }
+      return $operations;
+    }
+    // OpenStack user.
+    if ($entity->getEntityTypeId() === 'openstack_user') {
+      if ($account->hasPermission('edit openstack user')) {
+        $operations['change_password'] = [
+          'title' => $this->t('Change Password'),
+          'url' => $entity->toUrl('change-password-form'),
+          'weight' => 20,
+        ];
+      }
+      return $operations;
+    }
+    return $operations;
+  }
+
+  /**
+   * Implements hook_entity_operation_alter().
+   */
+  #[Hook('entity_operation_alter')]
+  public static function entityOperationAlter(array &$operations, EntityInterface $entity): void {
+    if ($entity->getEntityTypeId() === 'openstack_volume') {
+      if ($entity->getState() === 'in-use') {
+        unset($operations['delete']);
+      }
+    }
+    if ($entity->getEntityTypeId() === 'openstack_floating_ip') {
+      $association_id = $entity->getAssociationId();
+      if (isset($association_id)) {
+        unset($operations['delete']);
+      }
+    }
+    // OpenStack stack.
+    if ($entity->getEntityTypeId() === 'openstack_stack') {
+      if (empty($operations['edit'])) {
+        return;
+      }
+      // Remove destination query parameter for edit operation.
+      $url =& $operations['edit']['url'];
+      $options = $url->getOptions();
+      unset($options['query']['destination']);
+      $url->setOptions($options);
+    }
+  }
+
+  /**
+   * Implements hook_query_TAG_Alter().
+   */
+  #[Hook('query_openstack_floating_ip_views_access_alter')]
+  public static function queryOpenstackFloatingIpViewsAccessAlter(AlterableInterface $query): void {
+    if (!$account = $query->getMetaData('account')) {
+      $account = \Drupal::currentUser();
+    }
+    if ($account->hasPermission('view any openstack floating ip')) {
+      return;
+    }
+    else {
+      // Add a 'uid' condition.
+      $query->condition('openstack_floating_ip.uid', $account->id());
+    }
+  }
+
+  /**
+   * Implements hook_query_TAG_Alter().
+   */
+  #[Hook('query_openstack_instance_views_access_alter')]
+  public static function queryOpenstackInstanceViewsAccessAlter(AlterableInterface $query): void {
+    if (!$account = $query->getMetaData('account')) {
+      $account = \Drupal::currentUser();
+    }
+    if ($account->hasPermission('view any openstack instance')) {
+      return;
+    }
+    else {
+      // Add a 'uid' condition.
+      $query->condition('openstack_instance.uid', $account->id());
+    }
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_launch_template_openstack_add_form_alter')]
+  public static function formCloudLaunchTemplateOpenstackAddFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    openstack_form_cloud_launch_template_openstack_form_common_alter($form, $form_state, $form_id);
+    $route = \Drupal::routeMatch();
+    $cloud_context = $route->getParameter('cloud_context');
+    $openstack_service = \Drupal::service('openstack.factory')->isEc2ServiceType('cloud_config', $cloud_context);
+    if (empty($openstack_service)) {
+      // Overwrite function ::save.
+      $form['actions']['submit']['#submit'][1] = 'openstack_form_cloud_launch_template_openstack_add_form_submit';
+    }
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_launch_template_openstack_edit_form_alter')]
+  public static function formCloudLaunchTemplateOpenstackEditFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    openstack_form_cloud_launch_template_openstack_form_common_alter($form, $form_state, $form_id);
+    // Hide new revision checkbox.
+    $form['new_revision']['#access'] = FALSE;
+    // Disable name field.
+    $form['instance']['name']['#disabled'] = TRUE;
+    $server_template = $form_state->getFormObject()->getEntity();
+    $cloud_context = $server_template->getCloudContext();
+    $openstack_service = \Drupal::service('openstack.factory')->isEc2ServiceType('cloud_config', $cloud_context);
+    if (empty($openstack_service)) {
+      // Overwrite function ::save.
+      $form['actions']['submit']['#submit'][1] = 'openstack_form_cloud_launch_template_openstack_edit_form_submit';
+    }
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_launch_template_openstack_copy_form_alter')]
+  public function formCloudLaunchTemplateOpenstackCopyFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    openstack_form_cloud_launch_template_openstack_form_common_alter($form, $form_state, $form_id);
+    // Change name for copy.
+    $name = $form['instance']['name']['widget'][0]['value']['#default_value'];
+    $form['instance']['name']['widget'][0]['value']['#default_value'] = $this->t('copy_of_@name', [
+      '@name' => $name,
+    ]);
+    // Hide new revision checkbox.
+    $form['new_revision']['#access'] = FALSE;
+    // Clear the revision log message.
+    $form['others']['revision_log_message']['widget'][0]['value']['#default_value'] = NULL;
+    // Change value of the submit button.
+    $form['actions']['submit']['#value'] = $this->t('Copy');
+    // Delete the delete button.
+    $form['actions']['delete']['#access'] = FALSE;
+    // Overwrite function ::save.
+    $form['actions']['submit']['#submit'][1] = 'openstack_form_cloud_launch_template_openstack_copy_form_submit';
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_launch_template_openstack_delete_form_alter')]
+  public static function formCloudLaunchTemplateOpenstackDeleteFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    $form['actions']['submit']['#submit'] = [
+      'openstack_form_cloud_launch_template_openstack_delete_form_submit',
+    ];
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_launch_template_openstack_launch_form_alter')]
+  public function formCloudLaunchTemplateOpenstackLaunchFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    $route = \Drupal::routeMatch();
+    $cloud_context = $route->getParameter('cloud_context');
+    /** @var \Drupal\openstack\Service\OpenStackServiceInterface $openstack_service */
+    $openstack_ec2_service = \Drupal::service('openstack.factory')->isEc2ServiceType('cloud_config', $cloud_context);
+    $form['cost'] = [
+      '#type' => 'details',
+      '#title' => $this->t('Cost'),
+      '#open' => TRUE,
+    ];
+    // Add openstack_flavor.list view.
+    $view = Views::getView('openstack_flavor');
+    $view->setDisplay('list');
+    $pager_options = $view->display_handler->getOption('pager');
+    // Set the number of page links to 'Display all items'.
+    $pager_options['options']['items_per_page'] = 0;
+    // Disable exposing the 'items_per_page' option.
+    $pager_options['options']['expose']['items_per_page'] = FALSE;
+    $pager_options['expose']['items_per_page'] = FALSE;
+    $view->display_handler->setOption('pager', $pager_options);
+    $view->setArguments([
+      $cloud_context,
+    ]);
+    $form['cost']['flavor'] = $view->executeDisplay();
+    $form['#attached']['library'][] = 'openstack/openstack_launch_form';
+    if (!empty($openstack_ec2_service)) {
+      $form['automation'] = [
+        '#type' => 'details',
+        '#title' => $this->t('Automation'),
+        '#open' => TRUE,
+      ];
+      $form['automation']['description'] = $form['description'];
+      unset($form['description']);
+      $form['automation']['termination_protection'] = [
+        '#type' => 'checkbox',
+        '#title' => $this->t('Termination protection'),
+        '#description' => $this->t('Enable the termination protection. If enabled, this instance cannot be terminated using the console, API, or CLI until termination protection is disabled.'),
+        '#default_value' => $form_state->getFormObject()->getEntity()->get('field_termination_protection')->value === '1',
+      ];
+      $config = \Drupal::config('openstack.settings');
+      $form['automation']['terminate'] = [
+        '#type' => 'checkbox',
+        '#title' => $this->t('Automatically terminate instance'),
+        '#description' => $this->t('Terminate instance automatically.  Specify termination date in the date picker below.'),
+        '#default_value' => $config->get('openstack_instance_terminate'),
+      ];
+      // @todo make 30 days configurable
+      $form['automation']['termination_date'] = [
+        '#type' => 'datetime',
+        '#title' => $this->t('Termination Date'),
+        '#description' => $this->t('The default termination date is 30 days into the future.'),
+        '#default_value' => DrupalDateTime::createFromTimestamp(time() + 2592000),
+      ];
+    }
+    else {
+      $form['automation'] = [
+        '#type' => 'details',
+        '#title' => $this->t('Automation'),
+        '#open' => TRUE,
+      ];
+      $form['automation']['description'] = $form['description'];
+      unset($form['description']);
+      $form['automation']['termination_protection'] = [
+        '#type' => 'checkbox',
+        '#title' => $this->t('Termination protection'),
+        '#description' => $this->t('Enable the termination protection. If enabled, this instance cannot be terminated until termination protection is disabled.'),
+        '#default_value' => $form_state->getFormObject()->getEntity()->get('field_termination_protection')->value === '1',
+      ];
+    }
+    /** @var \Drupal\cloud\Entity\CloudLaunchTemplate $cloud_launch_template */
+    $cloud_launch_template = \Drupal::routeMatch()->getParameter('cloud_launch_template');
+    if ($cloud_launch_template->get('field_instance_shutdown_behavior')->value === 'terminate') {
+      // Add a warning message setting a schedule will terminate the instance,
+      // since the shutdown behavior is equal to 'terminate'.
+      $form['automation']['terminate_message'] = [
+        '#markup' => $this->t('Setting a schedule will potentially terminate the instance since the <strong>%text</strong> is set to Terminate', [
+          '%text' => 'Instance shutdown behavior',
+        ]),
+      ];
+    }
+    $view_builder = \Drupal::entityTypeManager()->getViewBuilder('cloud_launch_template');
+    $build = $view_builder->view($cloud_launch_template, 'view');
+    unset($build['#weight']);
+    $build['#pre_render'][] = '\Drupal\aws_cloud\Entity\Ec2\AwsCloudViewBuilder::reorderLaunchTemplate';
+    $form['detail'] = $build;
+    $form['#validate'][] = 'openstack_form_cloud_launch_template_openstack_launch_form_validate';
+  }
+
+  /**
+   * Implements hook_preprocess_menu_local_tasks().
+   */
+  #[Hook('preprocess_menu_local_tasks')]
+  public static function preprocessMenuLocalTasks(&$variables) {
+    // Hide the Revision tab from OpenStack cloud configs pages.
+    $routes = [
+      'entity.cloud_launch_template.canonical',
+      'entity.cloud_launch_template.edit_form',
+      'entity.cloud_launch_template.delete_form',
+      'entity.cloud_launch_template.launch',
+      'entity.cloud_launch_template.copy',
+    ];
+    $current_route = \Drupal::routeMatch();
+    if (in_array($current_route->getRouteName(), $routes, FALSE) === FALSE) {
+      return;
+    }
+    $cloud_context = $current_route->getParameter('cloud_context');
+    if (empty($cloud_context)) {
+      return;
+    }
+    /** @var \Drupal\cloud\Plugin\cloud\CloudPluginManager $cloud_plugin_manager */
+    $cloud_config_plugin = \Drupal::service('plugin.manager.cloud_config_plugin');
+    $cloud_config_plugin->setCloudContext($cloud_context);
+    $cloud_config = $cloud_config_plugin->loadConfigEntity();
+    if (empty($cloud_config)) {
+      return;
+    }
+    if ($cloud_config->bundle() === 'openstack') {
+      unset($variables['primary']['entity.cloud_launch_template.version_history']);
+    }
+  }
+
+  /**
+   * Implements hook_mail_alter().
+   */
+  #[Hook('mail_alter')]
+  public static function mailAlter(&$message) {
+    if ($message['key'] === 'quotas_request') {
+      $message['subject'] = $message['params']['subject'];
+      $message['body'][] = $message['params']['message'];
+      // HTML email.
+      $message['headers']['Content-Type'] = 'text/html';
+      $message['headers']['MIME-Version'] = '1.0';
+    }
+    elseif ($message['key'] === 'quotas_approved') {
+      $message['subject'] = $message['params']['subject'];
+      $message['body'][] = $message['params']['message'];
+    }
+  }
+
+  /**
+   * Implements hook_theme().
+   */
+  #[Hook('theme')]
+  public function theme($existing, $type, $theme, $path) {
+    return [
+      'usage_pie_chart' => [
+        'template' => 'usage_pie_chart',
+        'variables' => [
+          'title' => NULL,
+          'used' => NULL,
+          'total' => NULL,
+          'unit' => NULL,
+          'description_template' => $this->t('Used %d%s of %d%s'),
+        ],
+      ],
+    ];
+  }
+
+}
diff --git a/modules/cloud_service_providers/openstack/src/Hook/OpenstackTokensHooks.php b/modules/cloud_service_providers/openstack/src/Hook/OpenstackTokensHooks.php
new file mode 100644
index 0000000..659fd33
--- /dev/null
+++ b/modules/cloud_service_providers/openstack/src/Hook/OpenstackTokensHooks.php
@@ -0,0 +1,116 @@
+<?php
+
+namespace Drupal\openstack\Hook;
+
+use Drupal\Core\Render\BubbleableMetadata;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for openstack.
+ */
+class OpenstackTokensHooks {
+  use StringTranslationTrait;
+
+  /**
+   * Implements hook_token_info().
+   */
+  #[Hook('token_info')]
+  public function tokenInfo(): array {
+    $types['openstack_quota'] = [
+      'name' => $this->t('OpenStack quota'),
+      'description' => $this->t('Tokens related to individual OpenStack quota.'),
+      'needs-data' => 'openstack_quota',
+    ];
+    $quota['name'] = [
+      'name' => $this->t('Quota name'),
+      'description' => $this->t('Enter the name of the quota entity.'),
+    ];
+    $quota['quota_link'] = [
+      'name' => $this->t('Quota link'),
+      'description' => $this->t('An absolute link to quota.'),
+    ];
+    $quota['quota_edit_link'] = [
+      'name' => $this->t('Quota edit link'),
+      'description' => $this->t('An absolute link to edit the quota.'),
+    ];
+    $quota['changed'] = [
+      'name' => $this->t('Change date'),
+      'description' => $this->t('The quota change date.'),
+    ];
+    $types['openstack_quota_request'] = [
+      'name' => $this->t('OpenStack quota request'),
+      'description' => $this->t('Tokens related to individual OpenStack quota request.'),
+      'needs-data' => 'openstack_quota_request',
+    ];
+    $quota_request['name'] = [
+      'name' => $this->t('Quota name'),
+      'description' => $this->t('Enter the name of the quota entity.'),
+    ];
+    $quota_request['quota_link'] = [
+      'name' => $this->t('Quota link'),
+      'description' => $this->t('An absolute link to quota.'),
+    ];
+    $quota_request['quota_edit_link'] = [
+      'name' => $this->t('Quota edit link'),
+      'description' => $this->t('An absolute link to edit the quota.'),
+    ];
+    $quota_request['changed'] = [
+      'name' => $this->t('Change date'),
+      'description' => $this->t('The quota change date.'),
+    ];
+    $quota_request['quota_button_approve'] = [
+      'name' => $this->t('Approve button'),
+      'description' => $this->t('The quota approve button.'),
+    ];
+    $types['openstack_quota_email'] = [
+      'name' => $this->t('OpenStack quota email'),
+      'description' => $this->t('Tokens related to individual OpenStack quota email.'),
+      'needs-data' => 'openstack_quota_email',
+    ];
+    $quota_email['quotas'] = [
+      'name' => $this->t('List of quotas'),
+      'description' => $this->t('List of quotas to display to a user.'),
+    ];
+    $types['openstack_quota_request_email'] = [
+      'name' => $this->t('OpenStack quota request email'),
+      'description' => $this->t('Tokens related to individual OpenStack quota request email.'),
+      'needs-data' => 'openstack_quota_request_email',
+    ];
+    $openstack_request_email['quotas_request'] = [
+      'name' => $this->t('List of request quotas'),
+      'description' => $this->t('List of request quotas to display to a user.'),
+    ];
+    return [
+      'types' => $types,
+      'tokens' => [
+        'openstack_quota' => $quota,
+        'openstack_quota_request' => $quota_request,
+        'openstack_quota_email' => $quota_email,
+        'openstack_quota_request_email' => $openstack_request_email,
+      ],
+    ];
+  }
+
+  /**
+   * Implements hook_tokens().
+   */
+  #[Hook('tokens')]
+  public static function tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata): array {
+    $replacements = [];
+    if ($type === 'openstack_quota' && !empty($data['openstack_quota'])) {
+      $replacements = openstack_quota_tokens($tokens, $data);
+    }
+    elseif ($type === 'openstack_quota_request' && !empty($data['openstack_quota_request'])) {
+      $replacements = openstack_quota_request_tokens($tokens, $data);
+    }
+    elseif ($type === 'openstack_quota_email') {
+      $replacements = openstack_quota_email_tokens($tokens, $data);
+    }
+    elseif ($type === 'openstack_quota_request_email') {
+      $replacements = openstack_quota_request_email_tokens($tokens, $data);
+    }
+    return $replacements;
+  }
+
+}
diff --git a/modules/cloud_service_providers/terraform/src/Form/Config/TerraformAdminSettings.php b/modules/cloud_service_providers/terraform/src/Form/Config/TerraformAdminSettings.php
index 7d69430..7be6851 100644
--- a/modules/cloud_service_providers/terraform/src/Form/Config/TerraformAdminSettings.php
+++ b/modules/cloud_service_providers/terraform/src/Form/Config/TerraformAdminSettings.php
@@ -210,7 +210,7 @@ class TerraformAdminSettings extends ConfigFormBase {
 
     $views_settings = [];
     $views_settings['terraform_view_items_per_page'] = (int) $form_state->getValue('terraform_view_items_per_page');
-    $views_settings['terraform_view_expose_items_per_page'] = (boolean) $form_state->getValue('terraform_view_expose_items_per_page');
+    $views_settings['terraform_view_expose_items_per_page'] = (bool) $form_state->getValue('terraform_view_expose_items_per_page');
     $this->updateViewsPagerOptions('terraform', $views_settings);
 
     parent::submitForm($form, $form_state);
diff --git a/modules/cloud_service_providers/terraform/src/Hook/TerraformHooks.php b/modules/cloud_service_providers/terraform/src/Hook/TerraformHooks.php
new file mode 100644
index 0000000..6049822
--- /dev/null
+++ b/modules/cloud_service_providers/terraform/src/Hook/TerraformHooks.php
@@ -0,0 +1,185 @@
+<?php
+
+namespace Drupal\terraform\Hook;
+
+use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\cloud\Entity\CloudConfig;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for terraform.
+ */
+class TerraformHooks {
+  use StringTranslationTrait;
+
+  /**
+   * Implements hook_help().
+   */
+  #[Hook('help')]
+  public function help($route_name, RouteMatchInterface $route_match): string {
+    $output = '';
+    switch ($route_name) {
+      case 'help.page.terraform':
+        $output = '<h3>' . $this->t('About') . '</h3>';
+        $output .= '<ul>';
+        $output .= '<li>' . \Drupal::service('cloud')->getVersion() . '</li>';
+        $output .= '<li>' . $this->t('This module creates a user interface for managing Terraform Cloud.') . '</li>';
+        $output .= '</ul>';
+        $output .= '<h3>' . $this->t('Features') . '</h3>';
+        $output .= '<ul>';
+        $output .= '<li>' . $this->t('<strong>Terraform Cloud</strong>');
+        $output .= '<ul>';
+        $output .= '<li>' . $this->t('Manage Terraform Cloud.') . '</li>';
+        $output .= '<li>' . $this->t('Manage most of Terraform Cloud resources.') . '</li>';
+        $output .= '</ul></li></ul>';
+        $output .= '<p>' . $this->t('For more information, see the <a href=":cloud_documentation">online documentation for the Terraform Cloud module</a>.', [
+          ':cloud_documentation' => 'https://www.drupal.org/docs/8/modules/cloud',
+        ]) . '</p>';
+    }
+    return $output;
+  }
+
+  /**
+   * Implements hook_default_cloud_config_icon().
+   */
+  #[Hook('default_cloud_config_icon')]
+  public static function defaultCloudConfigIcon($entity): ?int {
+    // Provides the calling hook with the default Terraform Cloud icon.
+    return \Drupal::service('cloud')->getDefaultCloudConfigIcon($entity, 'terraform');
+  }
+
+  /**
+   * Implements hook_cloud_config_predelete().
+   */
+  #[Hook('cloud_config_predelete')]
+  public static function cloudConfigPredelete(CloudConfig $cloud_config): void {
+    if ($cloud_config->bundle() === 'terraform') {
+      $cloud_context = $cloud_config->getCloudContext();
+      /** @var \Drupal\terraform\Service\TerraformServiceInterface $terraform_service */
+      $terraform_service = \Drupal::service('terraform');
+      $terraform_service->setCloudContext($cloud_context);
+      \Drupal::service('cloud')->clearAllEntities('terraform', $cloud_context);
+      \Drupal::service('cloud')->deleteQueue(\Drupal::service('cloud')->getResourceQueueName($cloud_config));
+    }
+  }
+
+  /**
+   * Implements hook_cloud_config_delete().
+   */
+  #[Hook('cloud_config_delete')]
+  public static function cloudConfigDelete(CloudConfig $cloud_config): void {
+    if ($cloud_config->bundle() === 'terraform') {
+      // Clear cache cannot happen in hook_cloud_config_predelete().
+      // Moving it into hook_cloud_config_delete().
+      \Drupal::service('cloud')->clearAllCacheValues();
+    }
+  }
+
+  /**
+   * Implements hook_ENTITY_TYPE_predelete().
+   */
+  #[Hook('user_predelete')]
+  public static function userPredelete($account): void {
+    /** @var Drupal\cloud\Service\CloudService $cloud_service */
+    $cloud_service = \Drupal::service('cloud');
+    $entity_types = $cloud_service->getProviderEntityTypes('terraform');
+    foreach ($entity_types ?: [] as $entity_type) {
+      if (empty($entity_type)) {
+        continue;
+      }
+      $ids = \Drupal::entityTypeManager()->getStorage($entity_type->id())->getQuery()->accessCheck(TRUE)->condition('uid', $account->id())->execute();
+      if (count($ids) === 0) {
+        continue;
+      }
+      $cloud_service->reassignUids($ids, [
+        'uid' => 0,
+      ], $entity_type->id(), FALSE, $account);
+    }
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_config_terraform_edit_form_alter')]
+  public static function formCloudConfigTerraformEditFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    terraform_form_cloud_config_terraform_form_common_alter($form, $form_state, $form_id);
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_config_terraform_add_form_alter')]
+  public static function formCloudConfigTerraformAddFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    $form['cloud_context']['#access'] = FALSE;
+    $form['actions']['submit']['#submit'] = [
+      'terraform_form_cloud_config_terraform_add_form_submit',
+    ];
+    $form['#validate'][] = 'terraform_form_cloud_config_terraform_add_form_validate';
+    terraform_form_cloud_config_terraform_form_common_alter($form, $form_state, $form_id);
+  }
+
+  /**
+   * Implements hook_form_alter().
+   */
+  #[Hook('form_alter')]
+  public static function formAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    if (strpos($form_id, 'views_form_terraform_variable_') === 0) {
+      $form['#submit'][] = 'terraform_variable_views_bulk_form_submit';
+    }
+    elseif (strpos($form_id, 'views_form_terraform_') === 0) {
+      $form['#submit'][] = 'cloud_views_bulk_form_submit';
+    }
+  }
+
+  /**
+   * Implements hook_entity_operation().
+   */
+  #[Hook('entity_operation')]
+  public function entityOperation(EntityInterface $entity): array {
+    $operations = [];
+    $account = \Drupal::currentUser();
+    if ($entity->getEntityTypeId() === 'terraform_run') {
+      if ($account->hasPermission('edit terraform run')) {
+        if ($entity->getStatus() === 'planned') {
+          $operations['apply'] = [
+            'title' => $this->t('Apply'),
+            'url' => $entity->toUrl('apply-form'),
+            'weight' => 20,
+          ];
+        }
+      }
+    }
+    return $operations;
+  }
+
+  /**
+   * Implements hook_entity_view_alter().
+   */
+  #[Hook('entity_view_alter')]
+  public static function entityViewAlter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display): void {
+    if ($entity->getEntityTypeId() === 'terraform_run') {
+      $build['#attached']['library'][] = 'terraform/terraform_run';
+      $config = \Drupal::config('terraform.settings');
+      $build['#attached']['drupalSettings']['terraform']['terraform_js_refresh_interval'] = $config->get('terraform_js_refresh_interval');
+    }
+  }
+
+  /**
+   * Implements hook_cron().
+   */
+  #[Hook('cron')]
+  public static function cron(): void {
+    $terraform_service = \Drupal::service('terraform');
+    // Update resources.
+    $config_entities = \Drupal::service('plugin.manager.cloud_config_plugin')->loadConfigEntities('terraform');
+    foreach ($config_entities ?: [] as $config_entity) {
+      $terraform_service->setCloudContext($config_entity->getCloudContext());
+      $terraform_service->createResourceQueueItems();
+    }
+  }
+
+}
diff --git a/modules/cloud_service_providers/terraform/terraform.module b/modules/cloud_service_providers/terraform/terraform.module
index a0a798e..4cc2d1e 100644
--- a/modules/cloud_service_providers/terraform/terraform.module
+++ b/modules/cloud_service_providers/terraform/terraform.module
@@ -7,6 +7,8 @@
  * This module handles UI interactions with the cloud system for Terraform.
  */
 
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\terraform\Hook\TerraformHooks;
 use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
 use Drupal\Core\Entity\EntityInterface;
@@ -19,88 +21,41 @@ use Drupal\file\Entity\File;
 /**
  * Implements hook_help().
  */
+#[LegacyHook]
 function terraform_help($route_name, RouteMatchInterface $route_match): string {
-  $output = '';
-  switch ($route_name) {
-    case 'help.page.terraform':
-      $output = '<h3>' . t('About') . '</h3>';
-      $output .= '<ul>';
-      $output .= '<li>' . \Drupal::service('cloud')->getVersion() . '</li>';
-      $output .= '<li>' . t('This module creates a user interface for managing Terraform Cloud.') . '</li>';
-      $output .= '</ul>';
-      $output .= '<h3>' . t('Features') . '</h3>';
-      $output .= '<ul>';
-      $output .= '<li>' . t('<strong>Terraform Cloud</strong>');
-      $output .= '<ul>';
-      $output .= '<li>' . t('Manage Terraform Cloud.') . '</li>';
-      $output .= '<li>' . t('Manage most of Terraform Cloud resources.') . '</li>';
-      $output .= '</ul></li></ul>';
-      $output .= '<p>' . t('For more information, see the <a href=":cloud_documentation">online documentation for the Terraform Cloud module</a>.', [':cloud_documentation' => 'https://www.drupal.org/docs/8/modules/cloud']) . '</p>';
-
-  }
-  return $output;
+  return \Drupal::service(TerraformHooks::class)->help($route_name, $route_match);
 }
 
 /**
  * Implements hook_default_cloud_config_icon().
  */
+#[LegacyHook]
 function terraform_default_cloud_config_icon($entity): ?int {
-  // Provides the calling hook with the default Terraform Cloud icon.
-  return \Drupal::service('cloud')->getDefaultCloudConfigIcon($entity, 'terraform');
+  return \Drupal::service(TerraformHooks::class)->defaultCloudConfigIcon($entity);
 }
 
 /**
  * Implements hook_cloud_config_predelete().
  */
+#[LegacyHook]
 function terraform_cloud_config_predelete(CloudConfig $cloud_config): void {
-  if ($cloud_config->bundle() === 'terraform') {
-
-    $cloud_context = $cloud_config->getCloudContext();
-
-    /** @var \Drupal\terraform\Service\TerraformServiceInterface $terraform_service */
-    $terraform_service = \Drupal::service('terraform');
-    $terraform_service->setCloudContext($cloud_context);
-    \Drupal::service('cloud')->clearAllEntities('terraform', $cloud_context);
-
-    \Drupal::service('cloud')->deleteQueue(\Drupal::service('cloud')->getResourceQueueName($cloud_config));
-  }
+  \Drupal::service(TerraformHooks::class)->cloudConfigPredelete($cloud_config);
 }
 
 /**
  * Implements hook_cloud_config_delete().
  */
+#[LegacyHook]
 function terraform_cloud_config_delete(CloudConfig $cloud_config): void {
-  if ($cloud_config->bundle() === 'terraform') {
-    // Clear cache cannot happen in hook_cloud_config_predelete().
-    // Moving it into hook_cloud_config_delete().
-    \Drupal::service('cloud')->clearAllCacheValues();
-  }
+  \Drupal::service(TerraformHooks::class)->cloudConfigDelete($cloud_config);
 }
 
 /**
  * Implements hook_ENTITY_TYPE_predelete().
  */
+#[LegacyHook]
 function terraform_user_predelete($account): void {
-  /** @var Drupal\cloud\Service\CloudService $cloud_service */
-  $cloud_service = \Drupal::service('cloud');
-  $entity_types = $cloud_service->getProviderEntityTypes('terraform');
-  foreach ($entity_types ?: [] as $entity_type) {
-    if (empty($entity_type)) {
-      continue;
-    }
-    $ids = \Drupal::entityTypeManager()
-      ->getStorage($entity_type->id())
-      ->getQuery()
-      ->accessCheck(TRUE)
-      ->condition('uid', $account->id())
-      ->execute();
-    if (count($ids) === 0) {
-      continue;
-    }
-    $cloud_service->reassignUids($ids, [
-      'uid' => 0,
-    ], $entity_type->id(), FALSE, $account);
-  }
+  \Drupal::service(TerraformHooks::class)->userPredelete($account);
 }
 
 /**
@@ -150,19 +105,17 @@ function terraform_cloud_config_fieldsets(array &$fields): void {
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function terraform_form_cloud_config_terraform_edit_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  terraform_form_cloud_config_terraform_form_common_alter($form, $form_state, $form_id);
+  \Drupal::service(TerraformHooks::class)->formCloudConfigTerraformEditFormAlter($form, $form_state, $form_id);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function terraform_form_cloud_config_terraform_add_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  $form['cloud_context']['#access'] = FALSE;
-  $form['actions']['submit']['#submit'] = ['terraform_form_cloud_config_terraform_add_form_submit'];
-  $form['#validate'][] = 'terraform_form_cloud_config_terraform_add_form_validate';
-
-  terraform_form_cloud_config_terraform_form_common_alter($form, $form_state, $form_id);
+  \Drupal::service(TerraformHooks::class)->formCloudConfigTerraformAddFormAlter($form, $form_state, $form_id);
 }
 
 /**
@@ -276,13 +229,9 @@ function terraform_form_cloud_config_terraform_form_common_alter(array &$form, F
 /**
  * Implements hook_form_alter().
  */
+#[LegacyHook]
 function terraform_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  if (strpos($form_id, 'views_form_terraform_variable_') === 0) {
-    $form['#submit'][] = 'terraform_variable_views_bulk_form_submit';
-  }
-  elseif (strpos($form_id, 'views_form_terraform_') === 0) {
-    $form['#submit'][] = 'cloud_views_bulk_form_submit';
-  }
+  \Drupal::service(TerraformHooks::class)->formAlter($form, $form_state, $form_id);
 }
 
 /**
@@ -306,23 +255,9 @@ function terraform_variable_views_bulk_form_submit(array $form, FormStateInterfa
 /**
  * Implements hook_entity_operation().
  */
+#[LegacyHook]
 function terraform_entity_operation(EntityInterface $entity): array {
-  $operations = [];
-  $account = \Drupal::currentUser();
-
-  if ($entity->getEntityTypeId() === 'terraform_run') {
-    if ($account->hasPermission('edit terraform run')) {
-      if ($entity->getStatus() === 'planned') {
-        $operations['apply'] = [
-          'title' => t('Apply'),
-          'url' => $entity->toUrl('apply-form'),
-          'weight' => 20,
-        ];
-      }
-    }
-  }
-
-  return $operations;
+  return \Drupal::service(TerraformHooks::class)->entityOperation($entity);
 }
 
 /**
@@ -364,27 +299,15 @@ function terraform_aws_cloud_allowed_values_function(FieldStorageConfig $definit
 /**
  * Implements hook_entity_view_alter().
  */
+#[LegacyHook]
 function terraform_entity_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display): void {
-  if ($entity->getEntityTypeId() === 'terraform_run') {
-    $build['#attached']['library'][] = 'terraform/terraform_run';
-
-    $config = \Drupal::config('terraform.settings');
-    $build['#attached']['drupalSettings']['terraform']['terraform_js_refresh_interval']
-      = $config->get('terraform_js_refresh_interval');
-  }
+  \Drupal::service(TerraformHooks::class)->entityViewAlter($build, $entity, $display);
 }
 
 /**
  * Implements hook_cron().
  */
+#[LegacyHook]
 function terraform_cron(): void {
-  $terraform_service = \Drupal::service('terraform');
-
-  // Update resources.
-  $config_entities = \Drupal::service('plugin.manager.cloud_config_plugin')
-    ->loadConfigEntities('terraform');
-  foreach ($config_entities ?: [] as $config_entity) {
-    $terraform_service->setCloudContext($config_entity->getCloudContext());
-    $terraform_service->createResourceQueueItems();
-  }
+  \Drupal::service(TerraformHooks::class)->cron();
 }
diff --git a/modules/cloud_service_providers/terraform/terraform.services.yml b/modules/cloud_service_providers/terraform/terraform.services.yml
index 7ff5868..d7feeaf 100644
--- a/modules/cloud_service_providers/terraform/terraform.services.yml
+++ b/modules/cloud_service_providers/terraform/terraform.services.yml
@@ -6,3 +6,7 @@ services:
   terraform:
     factory: ['@terraform.factory', 'create']
     class: Drupal\terraform\Service\TerraformServiceInterface
+
+  Drupal\terraform\Hook\TerraformHooks:
+    class: Drupal\terraform\Hook\TerraformHooks
+    autowire: true
diff --git a/modules/cloud_service_providers/vmware/src/Form/Config/VmwareAdminSettings.php b/modules/cloud_service_providers/vmware/src/Form/Config/VmwareAdminSettings.php
index 853e290..15aca45 100644
--- a/modules/cloud_service_providers/vmware/src/Form/Config/VmwareAdminSettings.php
+++ b/modules/cloud_service_providers/vmware/src/Form/Config/VmwareAdminSettings.php
@@ -201,7 +201,7 @@ class VmwareAdminSettings extends ConfigFormBase {
 
     $views_settings = [];
     $views_settings['vmware_view_items_per_page'] = (int) $form_state->getValue('vmware_view_items_per_page');
-    $views_settings['vmware_view_expose_items_per_page'] = (boolean) $form_state->getValue('vmware_view_expose_items_per_page');
+    $views_settings['vmware_view_expose_items_per_page'] = (bool) $form_state->getValue('vmware_view_expose_items_per_page');
     $this->updateViewsPagerOptions('vmware', $views_settings);
 
     parent::submitForm($form, $form_state);
diff --git a/modules/cloud_service_providers/vmware/src/Hook/VmwareHooks.php b/modules/cloud_service_providers/vmware/src/Hook/VmwareHooks.php
new file mode 100644
index 0000000..1264715
--- /dev/null
+++ b/modules/cloud_service_providers/vmware/src/Hook/VmwareHooks.php
@@ -0,0 +1,279 @@
+<?php
+
+namespace Drupal\vmware\Hook;
+
+use Drupal\Core\Url;
+use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\cloud\Entity\CloudConfig;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for vmware.
+ */
+class VmwareHooks {
+  use StringTranslationTrait;
+
+  /**
+   * Implements hook_help().
+   */
+  #[Hook('help')]
+  public function help($route_name, RouteMatchInterface $route_match): string {
+    $output = '';
+    switch ($route_name) {
+      case 'help.page.vmware':
+        $output = '<h3>' . $this->t('About') . '</h3>';
+        $output .= '<ul>';
+        $output .= '<li>' . \Drupal::service('cloud')->getVersion() . '</li>';
+        $output .= '<li>' . $this->t('This module creates a user interface for managing VMware.') . '</li>';
+        $output .= '</ul>';
+        $output .= '<h3>' . $this->t('Features') . '</h3>';
+        $output .= '<ul>';
+        $output .= '<li>' . $this->t('<strong>VMware</strong>');
+        $output .= '<ul>';
+        $output .= '<li>' . $this->t('Manage VMware.') . '</li>';
+        $output .= '<li>' . $this->t('Manage most of VMware resources.') . '</li>';
+        $output .= '</ul></li></ul>';
+        $output .= '<p>' . $this->t('For more information, see the <a href=":cloud_documentation">online documentation for the VMware module</a>.', [
+          ':cloud_documentation' => 'https://www.drupal.org/docs/8/modules/cloud',
+        ]) . '</p>';
+    }
+    return $output;
+  }
+
+  /**
+   * Implements hook_default_cloud_config_icon().
+   */
+  #[Hook('default_cloud_config_icon')]
+  public static function defaultCloudConfigIcon(EntityInterface $entity): ?int {
+    // Provides the calling hook with the default VMware icon.
+    return \Drupal::service('cloud')->getDefaultCloudConfigIcon($entity, 'vmware');
+  }
+
+  /**
+   * Implements hook_cloud_config_predelete().
+   */
+  #[Hook('cloud_config_predelete')]
+  public static function cloudConfigPredelete(CloudConfig $cloud_config): void {
+    if ($cloud_config->bundle() === 'vmware') {
+      \Drupal::service('cloud')->deleteQueue(\Drupal::service('cloud')->getResourceQueueName($cloud_config));
+    }
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_config_vmware_edit_form_alter')]
+  public static function formCloudConfigVmwareEditFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    vmware_form_cloud_config_vmware_form_common_alter($form, $form_state, $form_id);
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_config_vmware_add_form_alter')]
+  public static function formCloudConfigVmwareAddFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    $form['cloud_context']['#access'] = FALSE;
+    $form['actions']['submit']['#submit'] = [
+      'vmware_form_cloud_config_vmware_add_form_submit',
+    ];
+    $form['#validate'][] = 'vmware_form_cloud_config_vmware_add_form_validate';
+    vmware_form_cloud_config_vmware_form_common_alter($form, $form_state, $form_id);
+  }
+
+  /**
+   * Implements hook_ENTITY_TYPE_view_alter().
+   */
+  #[Hook('cloud_config_view_alter')]
+  public static function cloudConfigViewAlter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display): void {
+    if ($entity->bundle() === 'vmware') {
+      $map_json_url = \Drupal::config('cloud.settings')->get('cloud_custom_location_map_json_url');
+      $url = Url::fromRoute('entity.cloud_config.location', [
+        'cloud_config' => $entity->id(),
+      ])->toString();
+      $build['cloud_config_location_map'] = [
+        '#markup' => '<div id="cloud_config_location"></div>',
+        '#attached' => [
+          'library' => [
+            'cloud/cloud_config_location',
+          ],
+          'drupalSettings' => [
+            'cloud' => [
+              'cloud_location_map_json_url' => $map_json_url,
+              'cloud_config_location_json_url' => $url,
+            ],
+          ],
+        ],
+      ];
+      $build['field_location_country']['#access'] = FALSE;
+      $build['field_location_city']['#access'] = FALSE;
+      $build['field_location_longitude']['#access'] = FALSE;
+      $build['field_location_latitude']['#access'] = FALSE;
+      vmware_cloud_config_fieldsets($build);
+    }
+  }
+
+  /**
+   * Implements hook_entity_operation().
+   */
+  #[Hook('entity_operation')]
+  public function entityOperation(EntityInterface $entity): array {
+    $operations = [];
+    $account = \Drupal::currentUser();
+    if ($entity->getEntityTypeId() === 'vmware_vm') {
+      if ($account->hasPermission('edit any vmware vm') || $account->hasPermission('edit own vmware vm') && !empty($entity->getOwner()) && $account->id() === $entity->getOwner()->id()) {
+        if ($entity->getPowerState() === 'POWERED_OFF') {
+          $operations['start'] = [
+            'title' => $this->t('Start'),
+            'url' => $entity->toUrl('start-form'),
+            'weight' => 20,
+          ];
+        }
+        elseif ($entity->getPowerState() === 'POWERED_ON') {
+          $operations['stop'] = [
+            'title' => $this->t('Stop'),
+            'url' => $entity->toUrl('stop-form'),
+            'weight' => 20,
+          ];
+          $operations['suspend'] = [
+            'title' => $this->t('Suspend'),
+            'url' => $entity->toUrl('suspend-form'),
+            'weight' => 30,
+          ];
+          $operations['reboot'] = [
+            'title' => $this->t('Reboot'),
+            'url' => $entity->toUrl('reboot-form'),
+            'weight' => 40,
+          ];
+        }
+        elseif ($entity->getPowerState() === 'SUSPENDED') {
+          $operations['start'] = [
+            'title' => $this->t('Start'),
+            'url' => $entity->toUrl('start-form'),
+            'weight' => 20,
+          ];
+          $operations['stop'] = [
+            'title' => $this->t('Stop'),
+            'url' => $entity->toUrl('stop-form'),
+            'weight' => 30,
+          ];
+        }
+      }
+    }
+    return $operations;
+  }
+
+  /**
+   * Implements hook_form_alter().
+   */
+  #[Hook('form_alter')]
+  public static function formAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    if (strpos($form_id, 'views_form_vmware_') === 0) {
+      $form['#submit'][] = 'cloud_views_bulk_form_submit';
+    }
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_launch_template_vmware_edit_form_alter')]
+  public static function formCloudLaunchTemplateVmwareEditFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    vmware_form_cloud_launch_template_vmware_form_common_alter($form, $form_state, $form_id);
+    // Hide new revision checkbox.
+    $form['new_revision']['#access'] = FALSE;
+    // Disable name field.
+    $form['instance']['name']['#disabled'] = TRUE;
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_launch_template_vmware_copy_form_alter')]
+  public function formCloudLaunchTemplateVmwareCopyFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    vmware_form_cloud_launch_template_vmware_form_common_alter($form, $form_state, $form_id);
+    // Hide new revision checkbox.
+    $form['new_revision']['#access'] = FALSE;
+    // Clear the revision log message.
+    $form['others']['revision_log_message']['widget'][0]['value']['#default_value'] = NULL;
+    // Change value of the submit button.
+    $form['actions']['submit']['#value'] = $this->t('Copy');
+    // Delete the delete button.
+    $form['actions']['delete']['#access'] = FALSE;
+    $form['actions']['submit']['#submit'][1] = 'vmware_form_cloud_launch_template_vmware_copy_form_submit';
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_cloud_launch_template_vmware_add_form_alter')]
+  public static function formCloudLaunchTemplateVmwareAddFormAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    vmware_form_cloud_launch_template_vmware_form_common_alter($form, $form_state, $form_id);
+  }
+
+  /**
+   * Implements hook_entity_view_alter().
+   *
+   * @throws \Drupal\vmware\Service\VmwareServiceException
+   *   Thrown when unable to get metrics nodes.
+   */
+  #[Hook('entity_view_alter')]
+  public static function entityViewAlter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display): void {
+    if ($entity->getEntityTypeId() === 'cloud_launch_template' && $entity->bundle() === 'vmware') {
+      \Drupal::service('cloud')->reorderForm($build, vmware_server_template_field_orders(FALSE));
+    }
+  }
+
+  /**
+   * Implements hook_cron().
+   */
+  #[Hook('cron')]
+  public static function cron(): void {
+    $vmware_service = \Drupal::service('vmware');
+    // Update resources.
+    $config_entities = \Drupal::service('plugin.manager.cloud_config_plugin')->loadConfigEntities('vmware');
+    foreach ($config_entities ?: [] as $config_entity) {
+      $vmware_service->setCloudContext($config_entity->getCloudContext());
+      $vmware_service->createResourceQueueItems();
+    }
+  }
+
+  /**
+   * Implements hook_cloud_config_delete().
+   */
+  #[Hook('cloud_config_delete')]
+  public static function cloudConfigDelete(CloudConfig $cloud_config): void {
+    if ($cloud_config->bundle() === 'vmware') {
+      // Clear cache cannot happen in hook_cloud_config_predelete().
+      // Moving it into hook_cloud_config_delete().
+      \Drupal::service('cloud')->clearAllCacheValues();
+    }
+  }
+
+  /**
+   * Implements hook_ENTITY_TYPE_predelete().
+   */
+  #[Hook('user_predelete')]
+  public static function userPredelete($account): void {
+    /** @var Drupal\cloud\Service\CloudService $cloud_service */
+    $cloud_service = \Drupal::service('cloud');
+    $entity_types = $cloud_service->getProviderEntityTypes('vmware');
+    foreach ($entity_types ?: [] as $entity_type) {
+      if (empty($entity_type)) {
+        continue;
+      }
+      $ids = \Drupal::entityTypeManager()->getStorage($entity_type->id())->getQuery()->accessCheck(TRUE)->condition('uid', $account->id())->execute();
+      if (count($ids) === 0) {
+        continue;
+      }
+      $cloud_service->reassignUids($ids, [
+        'uid' => 0,
+      ], $entity_type->id(), FALSE, $account, [
+        'vmware_reassign_entity_uid_callback',
+      ]);
+    }
+  }
+
+}
diff --git a/modules/cloud_service_providers/vmware/src/Service/VmwareService.php b/modules/cloud_service_providers/vmware/src/Service/VmwareService.php
index d8b78aa..9926819 100644
--- a/modules/cloud_service_providers/vmware/src/Service/VmwareService.php
+++ b/modules/cloud_service_providers/vmware/src/Service/VmwareService.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\vmware\Service;
 
+use Drupal\Component\Utility\DeprecationHelper;
 use Drupal\Component\Render\FormattableMarkup;
 use Drupal\Core\Batch\BatchBuilder;
 use Drupal\Core\Config\ConfigFactoryInterface;
@@ -608,7 +609,7 @@ class VmwareService extends CloudServiceBase implements VmwareServiceInterface {
    * Helper static method to clear cache.
    */
   public static function clearCacheValue(): void {
-    \Drupal::cache('menu')->invalidateAll();
+    DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => \Drupal::cache('menu')->deleteAll(), fn() => \Drupal::cache('menu')->invalidateAll());
     \Drupal::service('cache.render')->deleteAll();
     \Drupal::service('router.builder')->rebuild();
     \Drupal::service('plugin.cache_clearer')->clearCachedDefinitions();
diff --git a/modules/cloud_service_providers/vmware/vmware.module b/modules/cloud_service_providers/vmware/vmware.module
index 3431212..29eb5a3 100644
--- a/modules/cloud_service_providers/vmware/vmware.module
+++ b/modules/cloud_service_providers/vmware/vmware.module
@@ -7,6 +7,8 @@
  * This module handles UI interactions with the cloud system for VMware.
  */
 
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\vmware\Hook\VmwareHooks;
 use Drupal\Component\Utility\UrlHelper;
 use Drupal\Core\Database\Query\AlterableInterface;
 use Drupal\Core\Entity\ContentEntityInterface;
@@ -14,7 +16,6 @@ use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
-use Drupal\Core\Url;
 use Drupal\cloud\Entity\CloudConfig;
 use Drupal\field\Entity\FieldStorageConfig;
 use Drupal\file\Entity\File;
@@ -26,43 +27,25 @@ use Drupal\vmware\Service\VmwareServiceException;
 /**
  * Implements hook_help().
  */
+#[LegacyHook]
 function vmware_help($route_name, RouteMatchInterface $route_match): string {
-  $output = '';
-  switch ($route_name) {
-    case 'help.page.vmware':
-      $output = '<h3>' . t('About') . '</h3>';
-      $output .= '<ul>';
-      $output .= '<li>' . \Drupal::service('cloud')->getVersion() . '</li>';
-      $output .= '<li>' . t('This module creates a user interface for managing VMware.') . '</li>';
-      $output .= '</ul>';
-      $output .= '<h3>' . t('Features') . '</h3>';
-      $output .= '<ul>';
-      $output .= '<li>' . t('<strong>VMware</strong>');
-      $output .= '<ul>';
-      $output .= '<li>' . t('Manage VMware.') . '</li>';
-      $output .= '<li>' . t('Manage most of VMware resources.') . '</li>';
-      $output .= '</ul></li></ul>';
-      $output .= '<p>' . t('For more information, see the <a href=":cloud_documentation">online documentation for the VMware module</a>.', [':cloud_documentation' => 'https://www.drupal.org/docs/8/modules/cloud']) . '</p>';
-  }
-
-  return $output;
+  return \Drupal::service(VmwareHooks::class)->help($route_name, $route_match);
 }
 
 /**
  * Implements hook_default_cloud_config_icon().
  */
+#[LegacyHook]
 function vmware_default_cloud_config_icon(EntityInterface $entity): ?int {
-  // Provides the calling hook with the default VMware icon.
-  return \Drupal::service('cloud')->getDefaultCloudConfigIcon($entity, 'vmware');
+  return \Drupal::service(VmwareHooks::class)->defaultCloudConfigIcon($entity);
 }
 
 /**
  * Implements hook_cloud_config_predelete().
  */
+#[LegacyHook]
 function vmware_cloud_config_predelete(CloudConfig $cloud_config): void {
-  if ($cloud_config->bundle() === 'vmware') {
-    \Drupal::service('cloud')->deleteQueue(\Drupal::service('cloud')->getResourceQueueName($cloud_config));
-  }
+  \Drupal::service(VmwareHooks::class)->cloudConfigPredelete($cloud_config);
 }
 
 /**
@@ -124,19 +107,17 @@ function vmware_cloud_config_fieldsets(array &$fields): void {
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function vmware_form_cloud_config_vmware_edit_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  vmware_form_cloud_config_vmware_form_common_alter($form, $form_state, $form_id);
+  \Drupal::service(VmwareHooks::class)->formCloudConfigVmwareEditFormAlter($form, $form_state, $form_id);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function vmware_form_cloud_config_vmware_add_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  $form['cloud_context']['#access'] = FALSE;
-  $form['actions']['submit']['#submit'] = ['vmware_form_cloud_config_vmware_add_form_submit'];
-  $form['#validate'][] = 'vmware_form_cloud_config_vmware_add_form_validate';
-
-  vmware_form_cloud_config_vmware_form_common_alter($form, $form_state, $form_id);
+  \Drupal::service(VmwareHooks::class)->formCloudConfigVmwareAddFormAlter($form, $form_state, $form_id);
 }
 
 /**
@@ -262,97 +243,25 @@ function vmware_form_cloud_config_vmware_form_common_alter(array &$form, FormSta
 /**
  * Implements hook_ENTITY_TYPE_view_alter().
  */
+#[LegacyHook]
 function vmware_cloud_config_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display): void {
-  if ($entity->bundle() === 'vmware') {
-    $map_json_url = \Drupal::config('cloud.settings')->get('cloud_custom_location_map_json_url');
-    $url = Url::fromRoute('entity.cloud_config.location', ['cloud_config' => $entity->id()])
-      ->toString();
-
-    $build['cloud_config_location_map'] = [
-      '#markup' => '<div id="cloud_config_location"></div>',
-      '#attached' => [
-        'library' => [
-          'cloud/cloud_config_location',
-        ],
-        'drupalSettings' => [
-          'cloud' => [
-            'cloud_location_map_json_url' => $map_json_url,
-            'cloud_config_location_json_url' => $url,
-          ],
-        ],
-      ],
-    ];
-
-    $build['field_location_country']['#access'] = FALSE;
-    $build['field_location_city']['#access'] = FALSE;
-    $build['field_location_longitude']['#access'] = FALSE;
-    $build['field_location_latitude']['#access'] = FALSE;
-
-    vmware_cloud_config_fieldsets($build);
-  }
+  \Drupal::service(VmwareHooks::class)->cloudConfigViewAlter($build, $entity, $display);
 }
 
 /**
  * Implements hook_entity_operation().
  */
+#[LegacyHook]
 function vmware_entity_operation(EntityInterface $entity): array {
-  $operations = [];
-  $account = \Drupal::currentUser();
-
-  if ($entity->getEntityTypeId() === 'vmware_vm') {
-    if ($account->hasPermission('edit any vmware vm')
-        || ($account->hasPermission('edit own vmware vm')
-            && !empty($entity->getOwner())
-            && $account->id() === $entity->getOwner()->id())) {
-      if ($entity->getPowerState() === 'POWERED_OFF') {
-        $operations['start'] = [
-          'title' => t('Start'),
-          'url' => $entity->toUrl('start-form'),
-          'weight' => 20,
-        ];
-      }
-      elseif ($entity->getPowerState() === 'POWERED_ON') {
-        $operations['stop'] = [
-          'title' => t('Stop'),
-          'url' => $entity->toUrl('stop-form'),
-          'weight' => 20,
-        ];
-        $operations['suspend'] = [
-          'title' => t('Suspend'),
-          'url' => $entity->toUrl('suspend-form'),
-          'weight' => 30,
-        ];
-        $operations['reboot'] = [
-          'title' => t('Reboot'),
-          'url' => $entity->toUrl('reboot-form'),
-          'weight' => 40,
-        ];
-      }
-      elseif ($entity->getPowerState() === 'SUSPENDED') {
-        $operations['start'] = [
-          'title' => t('Start'),
-          'url' => $entity->toUrl('start-form'),
-          'weight' => 20,
-        ];
-        $operations['stop'] = [
-          'title' => t('Stop'),
-          'url' => $entity->toUrl('stop-form'),
-          'weight' => 30,
-        ];
-      }
-    }
-  }
-
-  return $operations;
+  return \Drupal::service(VmwareHooks::class)->entityOperation($entity);
 }
 
 /**
  * Implements hook_form_alter().
  */
+#[LegacyHook]
 function vmware_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  if (strpos($form_id, 'views_form_vmware_') === 0) {
-    $form['#submit'][] = 'cloud_views_bulk_form_submit';
-  }
+  \Drupal::service(VmwareHooks::class)->formAlter($form, $form_state, $form_id);
 }
 
 /**
@@ -400,35 +309,17 @@ function vmware_server_template_field_orders($include_name = TRUE): array {
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function vmware_form_cloud_launch_template_vmware_edit_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  vmware_form_cloud_launch_template_vmware_form_common_alter($form, $form_state, $form_id);
-
-  // Hide new revision checkbox.
-  $form['new_revision']['#access'] = FALSE;
-
-  // Disable name field.
-  $form['instance']['name']['#disabled'] = TRUE;
+  \Drupal::service(VmwareHooks::class)->formCloudLaunchTemplateVmwareEditFormAlter($form, $form_state, $form_id);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function vmware_form_cloud_launch_template_vmware_copy_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  vmware_form_cloud_launch_template_vmware_form_common_alter($form, $form_state, $form_id);
-
-  // Hide new revision checkbox.
-  $form['new_revision']['#access'] = FALSE;
-
-  // Clear the revision log message.
-  $form['others']['revision_log_message']['widget'][0]['value']['#default_value'] = NULL;
-
-  // Change value of the submit button.
-  $form['actions']['submit']['#value'] = t('Copy');
-
-  // Delete the delete button.
-  $form['actions']['delete']['#access'] = FALSE;
-
-  $form['actions']['submit']['#submit'][1] = 'vmware_form_cloud_launch_template_vmware_copy_form_submit';
+  \Drupal::service(VmwareHooks::class)->formCloudLaunchTemplateVmwareCopyFormAlter($form, $form_state, $form_id);
 }
 
 /**
@@ -465,8 +356,9 @@ function vmware_form_cloud_launch_template_vmware_copy_form_submit(array $form,
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function vmware_form_cloud_launch_template_vmware_add_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
-  vmware_form_cloud_launch_template_vmware_form_common_alter($form, $form_state, $form_id);
+  \Drupal::service(VmwareHooks::class)->formCloudLaunchTemplateVmwareAddFormAlter($form, $form_state, $form_id);
 }
 
 /**
@@ -508,37 +400,25 @@ function vmware_guest_os_allowed_values_function(FieldStorageConfig $definition,
  * @throws \Drupal\vmware\Service\VmwareServiceException
  *   Thrown when unable to get metrics nodes.
  */
+#[LegacyHook]
 function vmware_entity_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display): void {
-  if ($entity->getEntityTypeId() === 'cloud_launch_template'
-    && $entity->bundle() === 'vmware'
-  ) {
-    \Drupal::service('cloud')->reorderForm($build, vmware_server_template_field_orders(FALSE));
-  }
+  \Drupal::service(VmwareHooks::class)->entityViewAlter($build, $entity, $display);
 }
 
 /**
  * Implements hook_cron().
  */
+#[LegacyHook]
 function vmware_cron(): void {
-  $vmware_service = \Drupal::service('vmware');
-  // Update resources.
-  $config_entities = \Drupal::service('plugin.manager.cloud_config_plugin')
-    ->loadConfigEntities('vmware');
-  foreach ($config_entities ?: [] as $config_entity) {
-    $vmware_service->setCloudContext($config_entity->getCloudContext());
-    $vmware_service->createResourceQueueItems();
-  }
+  \Drupal::service(VmwareHooks::class)->cron();
 }
 
 /**
  * Implements hook_cloud_config_delete().
  */
+#[LegacyHook]
 function vmware_cloud_config_delete(CloudConfig $cloud_config): void {
-  if ($cloud_config->bundle() === 'vmware') {
-    // Clear cache cannot happen in hook_cloud_config_predelete().
-    // Moving it into hook_cloud_config_delete().
-    \Drupal::service('cloud')->clearAllCacheValues();
-  }
+  \Drupal::service(VmwareHooks::class)->cloudConfigDelete($cloud_config);
 }
 
 /**
@@ -572,29 +452,9 @@ function vmware_query_vmware_entity_views_access_alter(AlterableInterface $query
 /**
  * Implements hook_ENTITY_TYPE_predelete().
  */
+#[LegacyHook]
 function vmware_user_predelete($account): void {
-  /** @var Drupal\cloud\Service\CloudService $cloud_service */
-  $cloud_service = \Drupal::service('cloud');
-  $entity_types = $cloud_service->getProviderEntityTypes('vmware');
-  foreach ($entity_types ?: [] as $entity_type) {
-    if (empty($entity_type)) {
-      continue;
-    }
-    $ids = \Drupal::entityTypeManager()
-      ->getStorage($entity_type->id())
-      ->getQuery()
-      ->accessCheck(TRUE)
-      ->condition('uid', $account->id())
-      ->execute();
-    if (count($ids) === 0) {
-      continue;
-    }
-    $cloud_service->reassignUids($ids, [
-      'uid' => 0,
-    ], $entity_type->id(), FALSE, $account, [
-      'vmware_reassign_entity_uid_callback',
-    ]);
-  }
+  \Drupal::service(VmwareHooks::class)->userPredelete($account);
 }
 
 /**
diff --git a/modules/cloud_service_providers/vmware/vmware.services.yml b/modules/cloud_service_providers/vmware/vmware.services.yml
index 6ac9b28..2b565e6 100644
--- a/modules/cloud_service_providers/vmware/vmware.services.yml
+++ b/modules/cloud_service_providers/vmware/vmware.services.yml
@@ -14,3 +14,7 @@ services:
   vmware.operations:
     class: Drupal\vmware\Service\VmwareOperationsService
     arguments: ['@vmware']
+
+  Drupal\vmware\Hook\VmwareHooks:
+    class: Drupal\vmware\Hook\VmwareHooks
+    autowire: true
diff --git a/modules/gapps/gapps.module b/modules/gapps/gapps.module
index c22d9a0..4fa76f7 100644
--- a/modules/gapps/gapps.module
+++ b/modules/gapps/gapps.module
@@ -7,25 +7,16 @@
  * This module manage Google Applications.
  */
 
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\gapps\Hook\GappsHooks;
 use Drupal\Core\Routing\RouteMatchInterface;
 
 /**
  * Implements hook_help().
  */
+#[LegacyHook]
 function gapps_help($route_name, RouteMatchInterface $route_match): string {
-  switch ($route_name) {
-    case 'help.page.gapps':
-      $output = '<h3>' . t('About') . '</h3>';
-      $output .= '<ul>';
-      $output .= '<li>' . \Drupal::service('cloud')->getVersion() . '</li>';
-      $output .= '<li>' . t('The Google Applications module allows users to manage Google Application services.') . '</li>';
-      $output .= '</ul>';
-      $output .= '<p>' . t('For more information, see the <a href=":cloud_documentation">online documentation for the Google Applications module</a>.', [':cloud_documentation' => 'https://www.drupal.org/docs/8/modules/cloud']) . '</p>';
-      return $output;
-
-    default:
-      return '';
-  }
+  return \Drupal::service(GappsHooks::class)->help($route_name, $route_match);
 }
 
 /**
diff --git a/modules/gapps/gapps.services.yml b/modules/gapps/gapps.services.yml
index 5e8bbd9..6ecdba3 100644
--- a/modules/gapps/gapps.services.yml
+++ b/modules/gapps/gapps.services.yml
@@ -8,3 +8,7 @@ services:
     parent: default_plugin_manager
     tags:
       - { name: plugin_manager_cache_clear }
+
+  Drupal\gapps\Hook\GappsHooks:
+    class: Drupal\gapps\Hook\GappsHooks
+    autowire: true
diff --git a/modules/gapps/src/Hook/GappsHooks.php b/modules/gapps/src/Hook/GappsHooks.php
new file mode 100644
index 0000000..c8920a1
--- /dev/null
+++ b/modules/gapps/src/Hook/GappsHooks.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Drupal\gapps\Hook;
+
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for gapps.
+ */
+class GappsHooks {
+  use StringTranslationTrait;
+
+  /**
+   * Implements hook_help().
+   */
+  #[Hook('help')]
+  public function help($route_name, RouteMatchInterface $route_match): string {
+    switch ($route_name) {
+      case 'help.page.gapps':
+        $output = '<h3>' . $this->t('About') . '</h3>';
+        $output .= '<ul>';
+        $output .= '<li>' . \Drupal::service('cloud')->getVersion() . '</li>';
+        $output .= '<li>' . $this->t('The Google Applications module allows users to manage Google Application services.') . '</li>';
+        $output .= '</ul>';
+        $output .= '<p>' . $this->t('For more information, see the <a href=":cloud_documentation">online documentation for the Google Applications module</a>.', [
+          ':cloud_documentation' => 'https://www.drupal.org/docs/8/modules/cloud',
+        ]) . '</p>';
+        return $output;
+
+      default:
+        return '';
+    }
+  }
+
+}
diff --git a/modules/tools/k8s_to_s3/k8s_to_s3.module b/modules/tools/k8s_to_s3/k8s_to_s3.module
index f87a68f..e83eed5 100644
--- a/modules/tools/k8s_to_s3/k8s_to_s3.module
+++ b/modules/tools/k8s_to_s3/k8s_to_s3.module
@@ -7,6 +7,8 @@
  * This module transfers definitions of K8s resources to Amazon S3 bucket.
  */
 
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\k8s_to_s3\Hook\K8sToS3Hooks;
 use Drupal\Component\Serialization\Yaml;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Messenger\Messenger;
@@ -14,51 +16,22 @@ use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\aws_cloud\Service\Ecr\EcrServiceInterface;
 use Drupal\aws_cloud\Service\S3\S3ServiceInterface;
 use Drupal\cloud\Entity\CloudContentEntityBase;
-use Drupal\cloud\Plugin\cloud\config\CloudConfigPluginException;
 use Drupal\docker\Service\DockerServiceInterface;
 
 /**
  * Implements hook_help().
  */
+#[LegacyHook]
 function k8s_to_s3_help($route_name, RouteMatchInterface $route_match): string {
-  switch ($route_name) {
-    case 'help.page.k8s_to_s3':
-      $output = '<h3>' . t('About') . '</h3>';
-      $output .= '<ul>';
-      $output .= '<li>' . \Drupal::service('cloud')->getVersion() . '</li>';
-      $output .= '<li>' . t('The K8s to S3 module allows users to support the migration from K8s to the other K8s via S3 bucket.') . '</li>';
-      $output .= '</ul>';
-      $output .= '<p>' . t('For more information, see the <a href=":cloud_documentation">online documentation for the K8s to S3 module</a>.', [':cloud_documentation' => 'https://www.drupal.org/docs/8/modules/cloud']) . '</p>';
-      return $output;
-
-    default:
-      return '';
-  }
+  return \Drupal::service(K8sToS3Hooks::class)->help($route_name, $route_match);
 }
 
 /**
  * Implements hook_cron().
  */
+#[LegacyHook]
 function k8s_to_s3_cron(): void {
-  if (!k8s_to_s3_validate_config()) {
-    return;
-  }
-
-  $lock = \Drupal::lock();
-  $lock_name = $lock->getLockId('k8s_to_s3_export_entities');
-  if ($lock->acquire($lock_name)) {
-    k8s_to_s3_export_entities();
-    $lock->release($lock_name);
-  }
-
-  $config = \Drupal::config('k8s_to_s3.settings');
-  if ($config->get('enable_automatic_ecr_import_export') === TRUE) {
-    $lock_name = $lock->getLockId('k8s_to_s3_export_server_template');
-    if ($lock->acquire($lock_name)) {
-      k8s_to_s3_export_server_templates();
-      $lock->release($lock_name);
-    }
-  }
+  \Drupal::service(K8sToS3Hooks::class)->cron();
 }
 
 /**
@@ -491,91 +464,17 @@ function k8s_to_s3_build_k8s_s3_key(CloudContentEntityBase $entity, $entity_type
 /**
  * Implements hook_entity_delete().
  */
+#[LegacyHook]
 function k8s_to_s3_entity_delete(EntityInterface $entity): void {
-
-  $entity_type = $entity->getEntityTypeId();
-  if (!in_array($entity_type, k8s_get_exportable_entity_types())) {
-    return;
-  }
-
-  if (!k8s_to_s3_validate_config()) {
-    return;
-  }
-
-  $config = \Drupal::config('k8s_to_s3.settings');
-  $k8s_clusters = empty($config->get('k8s_clusters'))
-    ? []
-    : json_decode($config->get('k8s_clusters'), TRUE);
-  $aws_cloud_context = $config->get('aws_cloud');
-  $s3_bucket = $config->get('s3_bucket');
-
-  if (!in_array($entity->getCloudContext(), $k8s_clusters)) {
-    return;
-  }
-
-  $key = k8s_to_s3_build_k8s_s3_key($entity, $entity_type, TRUE);
-
-  $s3_service = \Drupal::service('aws_cloud.s3');
-  try {
-    $s3_service->setCloudContext($aws_cloud_context);
-  }
-  catch (CloudConfigPluginException $e) {
-    \Drupal::logger('k8s_to_s3')->error("{$e->getMessage()}: {$entity->label()}");
-    return;
-  }
-
-  k8s_to_s3_put_object($s3_service, $s3_bucket, $key, '');
+  \Drupal::service(K8sToS3Hooks::class)->entityDelete($entity);
 }
 
 /**
  * Implements hook_entity_presave().
  */
+#[LegacyHook]
 function k8s_to_s3_entity_presave(EntityInterface $entity): void {
-
-  if (!$entity->isNew()) {
-    return;
-  }
-
-  // Check whether the entity type is exportable or not.
-  $entity_type = $entity->getEntityTypeId();
-  if (!in_array($entity_type, k8s_get_exportable_entity_types())) {
-    return;
-  }
-
-  if (!k8s_to_s3_validate_config()) {
-    return;
-  }
-
-  if ($entity_type === 'k8s_namespace') {
-    $body = Yaml::encode([
-      'metadata' => [
-        'name' => $entity->getName(),
-      ],
-    ]);
-  }
-  else {
-    $body = $entity->getCreationYaml();
-  }
-
-  $config = \Drupal::config('k8s_to_s3.settings');
-  $k8s_clusters = empty($config->get('k8s_clusters'))
-    ? []
-    : json_decode($config->get('k8s_clusters'), TRUE);
-  $aws_cloud_context = $config->get('aws_cloud');
-  $s3_bucket = $config->get('s3_bucket');
-
-  if (!in_array($entity->getCloudContext(), $k8s_clusters)) {
-    return;
-  }
-
-  $key = k8s_to_s3_build_k8s_s3_key($entity, $entity_type);
-
-  $s3_service = \Drupal::service('aws_cloud.s3');
-  $s3_service->setCloudContext($aws_cloud_context);
-
-  if (!empty($body)) {
-    k8s_to_s3_put_object($s3_service, $s3_bucket, $key, $body);
-  }
+  \Drupal::service(K8sToS3Hooks::class)->entityPresave($entity);
 }
 
 /**
diff --git a/modules/tools/k8s_to_s3/k8s_to_s3.services.yml b/modules/tools/k8s_to_s3/k8s_to_s3.services.yml
new file mode 100644
index 0000000..8f50b1f
--- /dev/null
+++ b/modules/tools/k8s_to_s3/k8s_to_s3.services.yml
@@ -0,0 +1,5 @@
+
+services:
+  Drupal\k8s_to_s3\Hook\K8sToS3Hooks:
+    class: Drupal\k8s_to_s3\Hook\K8sToS3Hooks
+    autowire: true
diff --git a/modules/tools/k8s_to_s3/src/Hook/K8sToS3Hooks.php b/modules/tools/k8s_to_s3/src/Hook/K8sToS3Hooks.php
new file mode 100644
index 0000000..a52d7aa
--- /dev/null
+++ b/modules/tools/k8s_to_s3/src/Hook/K8sToS3Hooks.php
@@ -0,0 +1,136 @@
+<?php
+
+namespace Drupal\k8s_to_s3\Hook;
+
+use Drupal\Component\Serialization\Yaml;
+use Drupal\cloud\Plugin\cloud\config\CloudConfigPluginException;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for k8s_to_s3.
+ */
+class K8sToS3Hooks {
+  use StringTranslationTrait;
+
+  /**
+   * Implements hook_help().
+   */
+  #[Hook('help')]
+  public function help($route_name, RouteMatchInterface $route_match): string {
+    switch ($route_name) {
+      case 'help.page.k8s_to_s3':
+        $output = '<h3>' . $this->t('About') . '</h3>';
+        $output .= '<ul>';
+        $output .= '<li>' . \Drupal::service('cloud')->getVersion() . '</li>';
+        $output .= '<li>' . $this->t('The K8s to S3 module allows users to support the migration from K8s to the other K8s via S3 bucket.') . '</li>';
+        $output .= '</ul>';
+        $output .= '<p>' . $this->t('For more information, see the <a href=":cloud_documentation">online documentation for the K8s to S3 module</a>.', [
+          ':cloud_documentation' => 'https://www.drupal.org/docs/8/modules/cloud',
+        ]) . '</p>';
+        return $output;
+
+      default:
+        return '';
+    }
+  }
+
+  /**
+   * Implements hook_cron().
+   */
+  #[Hook('cron')]
+  public static function cron(): void {
+    if (!k8s_to_s3_validate_config()) {
+      return;
+    }
+    $lock = \Drupal::lock();
+    $lock_name = $lock->getLockId('k8s_to_s3_export_entities');
+    if ($lock->acquire($lock_name)) {
+      k8s_to_s3_export_entities();
+      $lock->release($lock_name);
+    }
+    $config = \Drupal::config('k8s_to_s3.settings');
+    if ($config->get('enable_automatic_ecr_import_export') === TRUE) {
+      $lock_name = $lock->getLockId('k8s_to_s3_export_server_template');
+      if ($lock->acquire($lock_name)) {
+        k8s_to_s3_export_server_templates();
+        $lock->release($lock_name);
+      }
+    }
+  }
+
+  /**
+   * Implements hook_entity_delete().
+   */
+  #[Hook('entity_delete')]
+  public static function entityDelete(EntityInterface $entity): void {
+    $entity_type = $entity->getEntityTypeId();
+    if (!in_array($entity_type, k8s_get_exportable_entity_types())) {
+      return;
+    }
+    if (!k8s_to_s3_validate_config()) {
+      return;
+    }
+    $config = \Drupal::config('k8s_to_s3.settings');
+    $k8s_clusters = empty($config->get('k8s_clusters')) ? [] : json_decode($config->get('k8s_clusters'), TRUE);
+    $aws_cloud_context = $config->get('aws_cloud');
+    $s3_bucket = $config->get('s3_bucket');
+    if (!in_array($entity->getCloudContext(), $k8s_clusters)) {
+      return;
+    }
+    $key = k8s_to_s3_build_k8s_s3_key($entity, $entity_type, TRUE);
+    $s3_service = \Drupal::service('aws_cloud.s3');
+    try {
+      $s3_service->setCloudContext($aws_cloud_context);
+    }
+    catch (CloudConfigPluginException $e) {
+      \Drupal::logger('k8s_to_s3')->error("{$e->getMessage()}: {$entity->label()}");
+      return;
+    }
+    k8s_to_s3_put_object($s3_service, $s3_bucket, $key, '');
+  }
+
+  /**
+   * Implements hook_entity_presave().
+   */
+  #[Hook('entity_presave')]
+  public static function entityPresave(EntityInterface $entity): void {
+    if (!$entity->isNew()) {
+      return;
+    }
+    // Check whether the entity type is exportable or not.
+    $entity_type = $entity->getEntityTypeId();
+    if (!in_array($entity_type, k8s_get_exportable_entity_types())) {
+      return;
+    }
+    if (!k8s_to_s3_validate_config()) {
+      return;
+    }
+    if ($entity_type === 'k8s_namespace') {
+      $body = Yaml::encode([
+        'metadata' => [
+          'name' => $entity->getName(),
+        ],
+      ]);
+    }
+    else {
+      $body = $entity->getCreationYaml();
+    }
+    $config = \Drupal::config('k8s_to_s3.settings');
+    $k8s_clusters = empty($config->get('k8s_clusters')) ? [] : json_decode($config->get('k8s_clusters'), TRUE);
+    $aws_cloud_context = $config->get('aws_cloud');
+    $s3_bucket = $config->get('s3_bucket');
+    if (!in_array($entity->getCloudContext(), $k8s_clusters)) {
+      return;
+    }
+    $key = k8s_to_s3_build_k8s_s3_key($entity, $entity_type);
+    $s3_service = \Drupal::service('aws_cloud.s3');
+    $s3_service->setCloudContext($aws_cloud_context);
+    if (!empty($body)) {
+      k8s_to_s3_put_object($s3_service, $s3_bucket, $key, $body);
+    }
+  }
+
+}
diff --git a/modules/tools/s3_to_k8s/s3_to_k8s.module b/modules/tools/s3_to_k8s/s3_to_k8s.module
index 780fe96..c471ab0 100644
--- a/modules/tools/s3_to_k8s/s3_to_k8s.module
+++ b/modules/tools/s3_to_k8s/s3_to_k8s.module
@@ -7,6 +7,8 @@
  * This module import definitions of K8s resources from Amazon S3 bucket.
  */
 
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\s3_to_k8s\Hook\S3ToK8sHooks;
 use Drupal\Component\Serialization\Yaml;
 use Drupal\Core\Messenger\Messenger;
 use Drupal\Core\Routing\RouteMatchInterface;
@@ -15,92 +17,17 @@ use Drupal\cloud\Service\CloudService;
 /**
  * Implements hook_help().
  */
+#[LegacyHook]
 function s3_to_k8s_help($route_name, RouteMatchInterface $route_match): string {
-  switch ($route_name) {
-    case 'help.page.s3_to_k8s':
-      $output = '<h3>' . t('About') . '</h3>';
-      $output .= '<ul>';
-      $output .= '<li>' . \Drupal::service('cloud')->getVersion() . '</li>';
-      $output .= '<li>' . t('The S3 to K8s module allows users to support the migration from K8s to the other K8s via S3 bucket.') . '</li>';
-      $output .= '</ul>';
-      $output .= '<p>' . t('For more information, see the <a href=":cloud_documentation">online documentation for the S3 to K8s module</a>.', [':cloud_documentation' => 'https://www.drupal.org/docs/8/modules/cloud']) . '</p>';
-      return $output;
-
-    default:
-      return '';
-  }
+  return \Drupal::service(S3ToK8sHooks::class)->help($route_name, $route_match);
 }
 
 /**
  * Implements hook_cron().
  */
+#[LegacyHook]
 function s3_to_k8s_cron(): void {
-  $config = \Drupal::config('s3_to_k8s.settings');
-
-  $aws_cloud_context = $config->get('aws_cloud');
-  if (empty($aws_cloud_context)) {
-    \Drupal::logger('s3_to_k8s')->error(t('The configuration aws_cloud is empty. Select an AWS Cloud provider in admin setting page.'));
-    return;
-  }
-
-  $s3_bucket = $config->get('s3_bucket');
-  if (empty($s3_bucket)) {
-    \Drupal::logger('s3_to_k8s')->error(t('The configuration s3_bucket is empty. Input a S3 Bucket in admin setting page.'));
-    return;
-  }
-
-  $s3_path = $config->get('s3_path');
-  if (empty($s3_path)) {
-    \Drupal::logger('s3_to_k8s')->error(t('The configuration s3_path is empty. Input a S3 Path in admin setting page.'));
-    return;
-  }
-
-  $k8s_cloud_context = $config->get('k8s_cluster') === 'Automatic'
-    ? s3_to_k8s_select_minimum_resource_cluster()
-    : $config->get('k8s_cluster');
-  if (empty($k8s_cloud_context)) {
-    \Drupal::logger('s3_to_k8s')->error(t('The configuration k8s_cluster is empty. Select K8s cluster in admin setting page.'));
-    return;
-  }
-
-  $s3_service = \Drupal::service('aws_cloud.s3');
-  $s3_service->setCloudContext($aws_cloud_context);
-  $result = $s3_service->listObjects([
-    'Bucket' => $s3_bucket,
-    'Prefix' => $s3_path,
-  ]);
-
-  if (empty($result['Contents'])) {
-    return;
-  }
-
-  // Update resources.
-  k8s_update_resources($k8s_cloud_context);
-
-  $entity_types = k8s_get_exportable_entity_types();
-
-  foreach ($entity_types ?: [] as $entity_type) {
-    s3_to_k8s_import_entities($entity_type, $aws_cloud_context, $s3_bucket, $s3_path, $k8s_cloud_context, $result['Contents']);
-  }
-
-  // Delete entities.
-  $s3_path = $s3_path . '.delete';
-  $s3_service->setCloudContext($aws_cloud_context);
-  $result = $s3_service->listObjects([
-    'Bucket' => $s3_bucket,
-    'Prefix' => $s3_path,
-  ]);
-
-  if (empty($result['Contents'])) {
-    return;
-  }
-
-  foreach ($entity_types ?: [] as $entity_type) {
-    s3_to_k8s_delete_entities($entity_type, $aws_cloud_context, $s3_bucket, $s3_path, $k8s_cloud_context, $result['Contents']);
-  }
-
-  // Update resources.
-  k8s_update_resources($k8s_cloud_context);
+  \Drupal::service(S3ToK8sHooks::class)->cron();
 }
 
 /**
diff --git a/modules/tools/s3_to_k8s/s3_to_k8s.services.yml b/modules/tools/s3_to_k8s/s3_to_k8s.services.yml
new file mode 100644
index 0000000..b1971ed
--- /dev/null
+++ b/modules/tools/s3_to_k8s/s3_to_k8s.services.yml
@@ -0,0 +1,5 @@
+
+services:
+  Drupal\s3_to_k8s\Hook\S3ToK8sHooks:
+    class: Drupal\s3_to_k8s\Hook\S3ToK8sHooks
+    autowire: true
diff --git a/modules/tools/s3_to_k8s/src/Hook/S3ToK8sHooks.php b/modules/tools/s3_to_k8s/src/Hook/S3ToK8sHooks.php
new file mode 100644
index 0000000..b9df42e
--- /dev/null
+++ b/modules/tools/s3_to_k8s/src/Hook/S3ToK8sHooks.php
@@ -0,0 +1,95 @@
+<?php
+
+namespace Drupal\s3_to_k8s\Hook;
+
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for s3_to_k8s.
+ */
+class S3ToK8sHooks {
+  use StringTranslationTrait;
+
+  /**
+   * Implements hook_help().
+   */
+  #[Hook('help')]
+  public function help($route_name, RouteMatchInterface $route_match): string {
+    switch ($route_name) {
+      case 'help.page.s3_to_k8s':
+        $output = '<h3>' . $this->t('About') . '</h3>';
+        $output .= '<ul>';
+        $output .= '<li>' . \Drupal::service('cloud')->getVersion() . '</li>';
+        $output .= '<li>' . $this->t('The S3 to K8s module allows users to support the migration from K8s to the other K8s via S3 bucket.') . '</li>';
+        $output .= '</ul>';
+        $output .= '<p>' . $this->t('For more information, see the <a href=":cloud_documentation">online documentation for the S3 to K8s module</a>.', [
+          ':cloud_documentation' => 'https://www.drupal.org/docs/8/modules/cloud',
+        ]) . '</p>';
+        return $output;
+
+      default:
+        return '';
+    }
+  }
+
+  /**
+   * Implements hook_cron().
+   */
+  #[Hook('cron')]
+  public function cron(): void {
+    $config = \Drupal::config('s3_to_k8s.settings');
+    $aws_cloud_context = $config->get('aws_cloud');
+    if (empty($aws_cloud_context)) {
+      \Drupal::logger('s3_to_k8s')->error($this->t('The configuration aws_cloud is empty. Select an AWS Cloud provider in admin setting page.'));
+      return;
+    }
+    $s3_bucket = $config->get('s3_bucket');
+    if (empty($s3_bucket)) {
+      \Drupal::logger('s3_to_k8s')->error($this->t('The configuration s3_bucket is empty. Input a S3 Bucket in admin setting page.'));
+      return;
+    }
+    $s3_path = $config->get('s3_path');
+    if (empty($s3_path)) {
+      \Drupal::logger('s3_to_k8s')->error($this->t('The configuration s3_path is empty. Input a S3 Path in admin setting page.'));
+      return;
+    }
+    $k8s_cloud_context = $config->get('k8s_cluster') === 'Automatic' ? s3_to_k8s_select_minimum_resource_cluster() : $config->get('k8s_cluster');
+    if (empty($k8s_cloud_context)) {
+      \Drupal::logger('s3_to_k8s')->error($this->t('The configuration k8s_cluster is empty. Select K8s cluster in admin setting page.'));
+      return;
+    }
+    $s3_service = \Drupal::service('aws_cloud.s3');
+    $s3_service->setCloudContext($aws_cloud_context);
+    $result = $s3_service->listObjects([
+      'Bucket' => $s3_bucket,
+      'Prefix' => $s3_path,
+    ]);
+    if (empty($result['Contents'])) {
+      return;
+    }
+    // Update resources.
+    k8s_update_resources($k8s_cloud_context);
+    $entity_types = k8s_get_exportable_entity_types();
+    foreach ($entity_types ?: [] as $entity_type) {
+      s3_to_k8s_import_entities($entity_type, $aws_cloud_context, $s3_bucket, $s3_path, $k8s_cloud_context, $result['Contents']);
+    }
+    // Delete entities.
+    $s3_path = $s3_path . '.delete';
+    $s3_service->setCloudContext($aws_cloud_context);
+    $result = $s3_service->listObjects([
+      'Bucket' => $s3_bucket,
+      'Prefix' => $s3_path,
+    ]);
+    if (empty($result['Contents'])) {
+      return;
+    }
+    foreach ($entity_types ?: [] as $entity_type) {
+      s3_to_k8s_delete_entities($entity_type, $aws_cloud_context, $s3_bucket, $s3_path, $k8s_cloud_context, $result['Contents']);
+    }
+    // Update resources.
+    k8s_update_resources($k8s_cloud_context);
+  }
+
+}
diff --git a/src/Form/Config/CloudAdminSettings.php b/src/Form/Config/CloudAdminSettings.php
index 6d4d0a9..c13ca7f 100644
--- a/src/Form/Config/CloudAdminSettings.php
+++ b/src/Form/Config/CloudAdminSettings.php
@@ -438,7 +438,7 @@ class CloudAdminSettings extends ConfigFormBase {
 
     $views_settings = [];
     $views_settings['cloud_view_items_per_page'] = (int) $form_state->getValue('cloud_view_items_per_page');
-    $views_settings['cloud_view_expose_items_per_page'] = (boolean) $form_state->getValue('cloud_view_expose_items_per_page');
+    $views_settings['cloud_view_expose_items_per_page'] = (bool) $form_state->getValue('cloud_view_expose_items_per_page');
     $this->updateViewsPagerOptions('cloud', $views_settings);
 
     $use_spa = !empty($form_state->getValue('cloud_use_spa'));
diff --git a/src/Hook/CloudHooks.php b/src/Hook/CloudHooks.php
new file mode 100644
index 0000000..7daaf90
--- /dev/null
+++ b/src/Hook/CloudHooks.php
@@ -0,0 +1,439 @@
+<?php
+
+namespace Drupal\cloud\Hook;
+
+use Drupal\cloud\Entity\CloudConfig;
+use Drupal\Component\Utility\Html;
+use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Url;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\views\Views;
+use Drupal\filter\FilterFormatRepositoryInterface;
+use Drupal\Component\Utility\DeprecationHelper;
+use Drupal\views\ViewExecutable;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for cloud.
+ */
+class CloudHooks {
+  use StringTranslationTrait;
+
+  /**
+   * Implements hook_cron().
+   */
+  #[Hook('cron')]
+  public static function cron() {
+    $config = \Drupal::config('cloud.settings');
+    if ($config->get('cloud_enable_queue_cleanup') === TRUE) {
+      // Clean out batch items older than 1 day.
+      \Drupal::database()->delete('queue')->condition('created', time() - 86400, '<')->condition('name', 'drupal_batch:%', 'LIKE')->execute();
+    }
+  }
+
+  /**
+   * Implements hook_help().
+   */
+  #[Hook('help')]
+  public function help($route_name, RouteMatchInterface $route_match): string {
+    switch ($route_name) {
+      case 'help.page.cloud':
+        $output = '<h3>' . $this->t('About') . '</h3>';
+        $output .= '<ul>';
+        $output .= '<li>' . \Drupal::service('cloud')->getVersion() . '</li>';
+        $output .= '<li>' . $this->t('The cloud module creates a user interface for users to manage clouds. Users can "Create instances", "Describe instances", and etc.') . '</li>';
+        $output .= '</ul>';
+        $output .= '<p>' . $this->t('For more information, see the <a href=":cloud_documentation">online documentation for the Cloud module</a>.', [
+          ':cloud_documentation' => 'https://www.drupal.org/docs/8/modules/cloud',
+        ]) . '</p>';
+        return $output;
+
+      case 'help.page.cloud_launch_template':
+        $output = '<h3>' . $this->t('About') . '</h3>';
+        $output .= '<p>' . $this->t('The launch template (a.k.a. cloud server template) module creates a user interface for users to manage clouds. Users can create launch templates.') . '</p>';
+        $output .= '<p>' . $this->t('For more information, see the <a href=":cloud_documentation">online documentation for the Cloud module</a>.', [
+          ':cloud_documentation' => 'https://www.drupal.org/docs/8/modules/cloud',
+        ]) . '</p>';
+        return $output;
+
+      default:
+        return '';
+    }
+  }
+
+  /**
+   * Implements hook_theme().
+   */
+  #[Hook('theme')]
+  public static function theme(): array {
+    $theme = [];
+    $theme['cloud_config'] = [
+      'render element' => 'elements',
+      'file' => 'cloud_config.page.inc',
+      'template' => 'cloud_config',
+    ];
+    $theme['cloud_config_content_add_list'] = [
+      'render element' => 'content',
+      'variables' => [
+        'content' => NULL,
+      ],
+      'file' => 'cloud_config.page.inc',
+    ];
+    $theme['cloud_launch_template'] = [
+      'render element' => 'elements',
+      'file' => 'cloud_launch_template.page.inc',
+      'template' => 'cloud_launch_template',
+    ];
+    $theme['cloud_launch_template_content_add_list'] = [
+      'render element' => 'content',
+      'variables' => [
+        'content' => NULL,
+      ],
+      'file' => 'cloud_launch_template.page.inc',
+    ];
+    return $theme;
+  }
+
+  /**
+   * Implements hook_theme_suggestions_HOOK().
+   */
+  #[Hook('theme_suggestions_cloud_config')]
+  public static function themeSuggestionsCloudConfig(array $variables): array {
+    $suggestions = [];
+    $entity = $variables['elements']['#cloud_config'];
+    $sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_');
+    $suggestions[] = 'cloud_config__' . $sanitized_view_mode;
+    $suggestions[] = 'cloud_config__' . $entity->bundle();
+    $suggestions[] = 'cloud_config__' . $entity->bundle() . '__' . $sanitized_view_mode;
+    $suggestions[] = 'cloud_config__' . $entity->id();
+    $suggestions[] = 'cloud_config__' . $entity->id() . '__' . $sanitized_view_mode;
+    return $suggestions;
+  }
+
+  /**
+   * Implements hook_theme_suggestions_HOOK().
+   */
+  #[Hook('theme_suggestions_cloud_launch_template')]
+  public static function themeSuggestionsCloudLaunchTemplate(array $variables): array {
+    $suggestions = [];
+    $entity = $variables['elements']['#cloud_launch_template'];
+    $sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_');
+    $suggestions[] = 'cloud_server_template__' . $sanitized_view_mode;
+    $suggestions[] = 'cloud_server_template__' . $entity->bundle();
+    $suggestions[] = 'cloud_server_template__' . $entity->bundle() . '__' . $sanitized_view_mode;
+    $suggestions[] = 'cloud_server_template__' . $entity->id();
+    $suggestions[] = 'cloud_server_template__' . $entity->id() . '__' . $sanitized_view_mode;
+    return $suggestions;
+  }
+
+  /**
+   * Implements hook_views_pre_render().
+   *
+   * This is a workaround to implement row level access control,
+   * until this issue is resolved:
+   * https://www.drupal.org/project/entity/issues/2909970
+   * Loop through the results,
+   * and call hasPermissions() with the entity's cloud_context.
+   * Unset the entity if the user does not have permissions.
+   */
+  #[Hook('views_pre_render')]
+  public function viewsPreRender(ViewExecutable $view): void {
+    $account = \Drupal::currentUser();
+    if ($view->id() === 'cloud_config' || $view->id() === 'cloud_launch_template') {
+      foreach ($view->result ?: [] as $key => $result) {
+        /** @var \Drupal\cloud\Entity\CloudConfigInterface $cloud */
+        $cloud = $result->_entity;
+        if (!$account->hasPermission('view ' . $cloud->getCloudContext()) && !$account->hasPermission('view all cloud service providers')) {
+          unset($view->result[$key]);
+        }
+      }
+    }
+    if (!empty($view->empty)) {
+      return;
+    }
+    $view->style_plugin->options['empty_table'] = TRUE;
+    $options = [
+      'id' => 'area',
+      'table' => 'views',
+      'field' => 'area',
+      'label' => '',
+      'relationship' => 'none',
+      'group_type' => 'group',
+      'empty' => TRUE,
+      'content' => [
+        'value' => $this->t('No items.'),
+        'format' => DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => \Drupal::service(FilterFormatRepositoryInterface::class)->getDefaultFormat()->id(), fn() => filter_default_format()),
+      ],
+    ];
+    $handler = DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => \Drupal::service('plugin.manager.views.area'), fn() => Views::handlerManager('area'))->getHandler($options);
+    $handler->init($view, $view->display_handler, $options);
+    $view->empty['area'] = $handler;
+  }
+
+  /**
+   * Implements hook_form_alter().
+   *
+   * Hook for form views_form_cloud_config_*.
+   */
+  #[Hook('form_alter')]
+  public static function formAlter(array &$form, FormStateInterface $form_state, $form_id): void {
+    if (strpos($form_id, 'views_form_cloud_config_') === 0) {
+      $form['#submit'][] = 'cloud_config_bulk_form_submit';
+    }
+    if (strpos($form_id, 'cloud_config_') === 0 && (strpos($form_id, 'add_form') !== FALSE || strpos($form_id, 'edit_form') !== FALSE) && \Drupal::service('cloud')->isGeocoderAvailable() === TRUE) {
+      $path = \Drupal::service('router.route_provider')->getRouteByName('entity.cloud_config.geocoder')->getPath();
+      $path = str_replace('{country}', 'country', $path);
+      $path = str_replace('{city}', 'city', $path);
+      $url = Url::fromUri('internal:' . $path);
+      $form['#attached']['library'] = 'cloud/cloud_geocoder';
+      $form['#attached']['drupalSettings']['cloud']['geocoder_url'] = $url->toString();
+    }
+  }
+
+  /**
+   * Implements hook_entity_view_alter().
+   */
+  #[Hook('entity_view_alter')]
+  public static function entityViewAlter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display): void {
+    $build['#attached']['library'][] = 'cloud/cloud_view_builder';
+  }
+
+  /**
+   * Implements hook_preprocess_views_view().
+   */
+  #[Hook('preprocess_views_view')]
+  public static function preprocessViewsView(array &$variables): void {
+    $view = $variables['view'];
+    if ($view->ajaxEnabled() && empty($view->is_attachment) && empty($view->live_preview)) {
+      // @todo https://www.drupal.org/project/drupal/issues/2866386
+      $view->element['#attached']['drupalSettings']['views']['ajaxViews']['views_dom_id:' . $view->dom_id]['view_path'] = \Drupal::routeMatch()->getRouteName() === 'views.ajax' ? \Drupal::service('path.current')->getPath() : Html::escape(Url::fromRoute('<current>')->toString());
+    }
+  }
+
+  /**
+   * Implements hook_library_info_alter().
+   */
+  #[Hook('library_info_alter')]
+  public static function libraryInfoAlter(&$libraries, $extension): void {
+    if ($extension === 'cloud') {
+      $config = \Drupal::configFactory()->getEditable('cloud.settings');
+      $source = $config->get('cloud_use_default_urls');
+      if (empty($source)) {
+        $libraries['d3']['js'] = [];
+        $libraries['d3-horizon-chart']['js'] = [];
+        $libraries['c3']['js'] = [];
+        $libraries['c3']['css']['theme'] = [];
+        $libraries['chartjs']['js'] = [];
+        $libraries['select2']['js'] = [];
+        $libraries['select2']['css']['theme'] = [];
+        $libraries['d3']['js'][trim($config->get('cloud_custom_d3_js_url') ?: '')] = [];
+        $libraries['d3-horizon-chart']['js'][trim($config->get('cloud_custom_d3_horizon_chart_js_url') ?: '')] = [];
+        $libraries['c3']['js'][trim($config->get('cloud_custom_c3_js_url') ?: '')] = [];
+        $libraries['c3']['css']['theme'][trim($config->get('cloud_custom_c3_css_url') ?: '')] = [];
+        $libraries['chartjs']['js'][trim($config->get('cloud_custom_chart_js_url') ?: '')] = [];
+        $libraries['select2']['js'][trim($config->get('cloud_custom_select2_js_url') ?: '')] = [];
+        $libraries['select2']['css']['theme'][trim($config->get('cloud_custom_select2_css_url') ?: '')] = [];
+        $libraries['datatables']['js'][trim($config->get('cloud_custom_datatables_js_url') ?: '')] = [];
+      }
+    }
+  }
+
+  /**
+   * Implements hook_preprocess_page().
+   */
+  #[Hook('preprocess_page')]
+  public static function preprocessPage(array &$variables): void {
+    $route = \Drupal::routeMatch()->getRouteObject();
+    if ($route) {
+      $view_id = $route->getDefault('view_id');
+      if ($view_id === 'cloud_config') {
+        $block_manager = \Drupal::service('plugin.manager.block');
+        $config = [
+          'not_display_cloud_config_page' => TRUE,
+        ];
+        $plugin_block = $block_manager->createInstance('cloud_config_location', $config);
+        $render = $plugin_block->build();
+        $variables['page']['content'] = [
+          $render,
+          $variables['page']['content'],
+        ];
+      }
+    }
+  }
+
+  /**
+   * Implements hook_field_widget_single_element_form_alter().
+   */
+  #[Hook('field_widget_single_element_form_alter')]
+  public static function fieldWidgetSingleElementFormAlter(&$element, FormStateInterface $form_state, array $context): void {
+    // The new field widget hook hook_field_widget_complete_form_alter was
+    // introduced to make it possible to alter the whole widget form after it was
+    // processed.
+    // hook_field_widget_form_alter was marked as deprecated and will be replaced
+    // by hook_field_widget_single_element_form_alter.  Also,
+    // hook_field_widget_multivalue_form_alter were marked as deprecated and will
+    // be removed in Drupal 10.
+    // Therefore, we replaced hook_field_widget_multivalue_form_alter to
+    // hook_field_widget_single_element_form_alter for Drupal 9.2.
+    $field_definition = $context['items']->getFieldDefinition();
+    $settings = $field_definition->getSettings();
+    if ($field_definition->getName() === 'field_location_latitude' || $field_definition->getName() === 'field_location_longitude') {
+      // @todo Prevents validation of decimal numbers.
+      // @see https://www.drupal.org/node/2230909
+      $element['value']['#step'] = 'any';
+      $element['value']['#scale'] = $settings['scale'];
+    }
+  }
+
+  /**
+   * Implements hook_preprocess_input__number().
+   */
+  #[Hook('preprocess_input__number')]
+  public static function preprocessInputNumber(array &$variables): void {
+    $element =& $variables['element'];
+    if (str_contains($element['#name'], 'field_location_latitude') || str_contains($element['#name'], 'field_location_longitude') || str_contains($element['#name'], 'aws_cloud_region_locations')) {
+      // @todo Enable the validation only on the browser for step settings.
+      // @see https://www.drupal.org/node/2230909
+      $scale = $element['#scale'];
+      $variables['attributes']['step'] = 0.1 ** $scale;
+    }
+  }
+
+  /**
+   * Implements hook_modules_installed().
+   */
+  #[Hook('modules_installed')]
+  public static function modulesInstalled($modules): void {
+    if (!in_array('cloud', $modules) && !in_array('geocoder', $modules) || in_array('cloud', $modules) && !\Drupal::moduleHandler()->moduleExists('geocoder')) {
+      return;
+    }
+    // Initialize the geocoder provider.
+    $cloud_service = \Drupal::getContainer()->get('cloud');
+    $cloud_service->initGeocoder();
+  }
+
+  /**
+   * Implements hook_preprocess_HOOK().
+   */
+  #[Hook('preprocess_menu')]
+  public static function preprocessMenu(&$variables): void {
+    // Remove cloud service provider menus without sub-menu.
+    if (!empty($variables['menu_name']) && $variables['menu_name'] === 'main') {
+      $variables['#cache']['max-age'] = 0;
+      if (empty($variables['items'])) {
+        return;
+      }
+      foreach ([
+        'cloud.service_providers.menu',
+        'cloud.design.menu',
+      ] as $menu) {
+        if (empty($variables['items'][$menu])) {
+          continue;
+        }
+        foreach ($parent =& $variables['items'][$menu]['below'] as $key => $item) {
+          // Skip the menu "add cloud service provider".
+          if ($key === 'cloud.menu.cloud_links:cloud_service_provider.local_tasks') {
+            continue;
+          }
+          // If there is no sub-menu, remove the parent menu.
+          if (empty($item['below'])) {
+            unset($parent[$key]);
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Implements hook_mail().
+   */
+  #[Hook('mail')]
+  public static function mail($key, array &$message, array $params): void {
+    // This hook is called from MailManagerInterface->mail(). Note that
+    // hook_mail(),
+    // unlike hook_mail_alter(), is only called on the $module argument to
+    // MailManagerInterface->mail(), not all modules.
+    switch ($key) {
+      case 'notify_entity_admins_users':
+        $message['subject'] = $params['subject'];
+        $message['body'][] = $params['message'];
+        break;
+    }
+  }
+
+  /**
+   * Implements hook_cloud_config_predelete().
+   */
+  #[Hook('cloud_config_predelete')]
+  public static function cloudConfigPredelete(CloudConfig $cloud_config): void {
+    // Delete all cloud project entities.
+    $storage = \Drupal::entityTypeManager()->getStorage('cloud_project');
+    $entities = $storage->loadByProperties([
+      'cloud_context' => $cloud_config->getCloudContext(),
+      'type' => $cloud_config->bundle(),
+    ]);
+    if (!empty($entities)) {
+      $storage->delete($entities);
+    }
+    // Delete cached operation data, if exists.
+    $cache_service = \Drupal::service('cloud.cache');
+    $cache_service->clearOperationAccessCache($cloud_config->getCloudContext());
+  }
+
+  /**
+   * Implements hook_ENTITY_TYPE_predelete().
+   */
+  #[Hook('user_predelete')]
+  public static function userPredelete($account): void {
+    // Reassign entities from the user being deleted.
+    $cloud_config_ids = \Drupal::entityTypeManager()->getStorage('cloud_config')->userRevisionIds($account);
+    // Reassign cloud config entities.
+    \Drupal::service('cloud')->reassignUids($cloud_config_ids, [
+      'uid' => 0,
+      'revision_uid' => 0,
+    ], 'cloud_config', TRUE, $account);
+    $cloud_project_ids = \Drupal::entityTypeManager()->getStorage('cloud_project')->userRevisionIds($account);
+    // Reassign cloud project entities.
+    \Drupal::service('cloud')->reassignUids($cloud_project_ids, [
+      'uid' => 0,
+      'revision_uid' => 0,
+    ], 'cloud_project', TRUE, $account);
+    // Reassign cloud store entities.
+    $cloud_store_ids = \Drupal::entityTypeManager()->getStorage('cloud_store')->getQuery()->accessCheck(TRUE)->condition('uid', $account->id())->execute();
+    \Drupal::service('cloud')->reassignUids($cloud_store_ids, [
+      'uid' => 0,
+    ], 'cloud_store', FALSE, $account);
+  }
+
+  /**
+   * Implements hook_user_cancel_methods_alter().
+   *
+   * Add additional description informing users that cloud related
+   * entities are not deleted from the system.  They will be reassigned.
+   */
+  #[Hook('user_cancel_methods_alter')]
+  public function userCancelMethodsAlter(&$methods): void {
+    $user_settings = \Drupal::config('user.settings');
+    $anonymous_name = $user_settings->get('anonymous');
+    $methods['user_cancel_block']['title'] = $this->t('Disable the account and keep its content. Entities provided by Cloud modules are not affected.');
+    $methods['user_cancel_block']['description'] = $this->t('Your account will be blocked and you will no longer be able to log in. All of your content will remain attributed to your username. Entities provided by Cloud modules are not affected and remain attributed to your username.');
+    $methods['user_cancel_block_unpublish']['title'] = $this->t('Disable the account and unpublish its content. Entities provided by Cloud modules are not affected');
+    $methods['user_cancel_block_unpublish']['description'] = $this->t('Your account will be blocked and you will no longer be able to log in. All of your content will be hidden from everyone but administrators. Entities provided by Cloud modules not affected and remain attributed to your username.');
+    $methods['user_cancel_reassign']['title'] = $this->t('Delete the account and make its content belong to the %anonymous-name user. Entities provided by Cloud modules are reassigned to %anonymous-name user. This action cannot be undone.', [
+      '%anonymous-name' => $anonymous_name,
+    ]);
+    $methods['user_cancel_reassign']['description'] = $this->t('Your account will be removed and all account information deleted. All of your content will be assigned to the %anonymous-name user.  Entities provided by Cloud modules are reassigned to %anonymous-name user.', [
+      '%anonymous-name' => $anonymous_name,
+    ]);
+    $methods['user_cancel_delete']['title'] = $this->t('Delete the account and its content. Entities provided by Cloud modules are reassigned to %anonymous-name user. This action cannot be undone.', [
+      '%anonymous-name' => $anonymous_name,
+    ]);
+    $methods['user_cancel_delete']['description'] = $this->t('Your account will be removed and all account information deleted. All of your content will also be deleted. Entities provided by Cloud modules are not deleted but reassigned to %anonymous-name user.', [
+      '%anonymous-name' => $anonymous_name,
+    ]);
+  }
+
+}
diff --git a/src/Plugin/cloud/launch_template/CloudLaunchTemplatePluginUninstallValidator.php b/src/Plugin/cloud/launch_template/CloudLaunchTemplatePluginUninstallValidator.php
index 2d369c7..7b6a960 100644
--- a/src/Plugin/cloud/launch_template/CloudLaunchTemplatePluginUninstallValidator.php
+++ b/src/Plugin/cloud/launch_template/CloudLaunchTemplatePluginUninstallValidator.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\cloud\Plugin\cloud\launch_template;
 
+use Drupal\Component\Utility\DeprecationHelper;
 use Drupal\Core\Entity\ContentEntityTypeInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Extension\ModuleUninstallValidatorInterface;
@@ -37,7 +38,7 @@ class CloudLaunchTemplatePluginUninstallValidator implements ModuleUninstallVali
    */
   public function validate($module): array {
 
-    field_purge_batch(10000);
+    DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => \Drupal::service('Drupal\Core\Field\FieldPurger')->purgeBatch(10000), fn() => field_purge_batch(10000));
 
     // @todo As long as CloudLaunchTemplateType for each bundle is deleted by
     // Drupal core, we do not have to handle UninstallValidator, therefore this
diff --git a/src/Plugin/cloud/project/CloudProjectPluginUninstallValidator.php b/src/Plugin/cloud/project/CloudProjectPluginUninstallValidator.php
index 0b8bdff..5528101 100644
--- a/src/Plugin/cloud/project/CloudProjectPluginUninstallValidator.php
+++ b/src/Plugin/cloud/project/CloudProjectPluginUninstallValidator.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\cloud\Plugin\cloud\project;
 
+use Drupal\Component\Utility\DeprecationHelper;
 use Drupal\Core\Entity\ContentEntityTypeInterface;
 use Drupal\Core\Entity\EntityTypeManager;
 use Drupal\Core\Extension\ModuleUninstallValidatorInterface;
@@ -44,7 +45,7 @@ class CloudProjectPluginUninstallValidator implements ModuleUninstallValidatorIn
    */
   public function validate($module): array {
 
-    field_purge_batch(10000);
+    DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => \Drupal::service('Drupal\Core\Field\FieldPurger')->purgeBatch(10000), fn() => field_purge_batch(10000));
 
     // First, check if there are entities related to $module.
     $entity_types = $this->entityTypeManager->getDefinitions();
diff --git a/src/Plugin/cloud/store/CloudStorePluginUninstallValidator.php b/src/Plugin/cloud/store/CloudStorePluginUninstallValidator.php
index dae592e..16a308c 100644
--- a/src/Plugin/cloud/store/CloudStorePluginUninstallValidator.php
+++ b/src/Plugin/cloud/store/CloudStorePluginUninstallValidator.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\cloud\Plugin\cloud\store;
 
+use Drupal\Component\Utility\DeprecationHelper;
 use Drupal\Core\Entity\ContentEntityTypeInterface;
 use Drupal\Core\Entity\EntityTypeManager;
 use Drupal\Core\Extension\ModuleUninstallValidatorInterface;
@@ -44,7 +45,7 @@ class CloudStorePluginUninstallValidator implements ModuleUninstallValidatorInte
    */
   public function validate($module): array {
 
-    field_purge_batch(10000);
+    DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => \Drupal::service('Drupal\Core\Field\FieldPurger')->purgeBatch(10000), fn() => field_purge_batch(10000));
 
     // First, check if there are entities related to $module.
     $entity_types = $this->entityTypeManager->getDefinitions();
diff --git a/src/Service/CloudService.php b/src/Service/CloudService.php
index 9518c82..2570dfd 100644
--- a/src/Service/CloudService.php
+++ b/src/Service/CloudService.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\cloud\Service;
 
+use Drupal\Component\Utility\DeprecationHelper;
 use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
 use Drupal\Component\Plugin\Exception\PluginNotFoundException;
 use Drupal\Component\Serialization\Yaml;
@@ -1405,7 +1406,7 @@ class CloudService extends CloudServiceBase implements CloudServiceInterface, Cl
     foreach ($options ?: [] as $key => $value) {
       $view->set($key, $value);
     }
-    $view->save(TRUE);
+    DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => $view->save(), fn() => $view->save(TRUE));
   }
 
   /**
@@ -1459,7 +1460,7 @@ class CloudService extends CloudServiceBase implements CloudServiceInterface, Cl
     if (!empty($tags)) {
       $this->cacheTagsInvalidator->invalidateTags($tags);
     }
-    $this->cachedMenu->invalidateAll();
+    DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => $this->cachedMenu->deleteAll(), fn() => $this->cachedMenu->invalidateAll());
     $this->cacheRenderer->deleteAll();
     $this->routerBuilder->rebuild();
     $this->cachedDiscoveryClearer->clearCachedDefinitions();
@@ -1782,7 +1783,7 @@ class CloudService extends CloudServiceBase implements CloudServiceInterface, Cl
    */
   public static function updateEntityValues(EntityInterface $entity, array $updates): void {
     try {
-      $entity->original = clone $entity;
+      DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => $entity->setOriginal(clone $entity), fn() => $entity->original = clone $entity);
       foreach ($updates ?: [] as $name => $value) {
         $entity->$name = $value;
       }
diff --git a/tests/src/Functional/Module/CloudModuleTestBase.php b/tests/src/Functional/Module/CloudModuleTestBase.php
index a9129bc..5e48203 100644
--- a/tests/src/Functional/Module/CloudModuleTestBase.php
+++ b/tests/src/Functional/Module/CloudModuleTestBase.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Tests\cloud\Functional\Module;
 
+use Drupal\Component\Utility\DeprecationHelper;
 use Drupal\Core\Entity\ContentEntityTypeInterface;
 use Drupal\Core\Logger\RfcLogLevel;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
@@ -133,7 +134,7 @@ abstract class CloudModuleTestBase extends ModuleTestBase {
     }
 
     // Purge the field data.
-    field_purge_batch(1000);
+    DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => \Drupal::service('Drupal\Core\Field\FieldPurger')->purgeBatch(1000), fn() => field_purge_batch(1000));
 
     $all_modules = \Drupal::service('extension.list.module')->getList();
     $database_module = \Drupal::service('database')->getProvider();
