Hello and thanks for this module,

Problem/Motivation

Since the Hetzner is popular in Europe, wouldn't it be nice to have Hetzner as a cloud provider?

Best,
Orkut

Issue fork cloud-3576337

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

orkut murat yılmaz created an issue. See original summary.

erdinchas made their first commit to this issue’s fork.

erdinchas’s picture

Assigned: Unassigned » erdinchas

I'm working on it =)

erdinchas’s picture

Status: Active » Needs review

@orkut could you please check?

orkutmuratyilmaz’s picture

Status: Needs review » Needs work

@erdinchas, thanks for your contribution:)

Generally, the submodule looks working, but it has some phpcs, phpstan and phpunit errors. Can you solve them too?

erdinchas’s picture

Assigned: erdinchas » Unassigned
Status: Needs work » Needs review

Could you please check again?

orkutmuratyilmaz’s picture

Status: Needs review » Reviewed & tested by the community

Thank you @erdinchas, it works:)

baldwinlouie’s picture

Status: Reviewed & tested by the community » Needs work
StatusFileSize
new175.55 KB

@erdinchas. Thank you so much for the new module. This looks like hard work to implement.

Before getting this merged, I tested it against a fresh installation of Drupal 11. I'm getting the following message when enabling Hetzner Cloud

Unable to install Hetzner Cloud, field.storage.cloud_config.field_api_endpoint already exists in active configuration.

field.storage.cloud_config.field_api_endpoint.yml is declared in the OpenStack submodule. If users have the OpenStack module enabled, Hetzner Cloud will encounter this installation error.

One option is to rename the Hetzner Cloud's field_api_endpoint to something else.

Secondly, can you replace the cloud/modules/cloud_service_providers/hetzner_cloud/images/hetzner_cloud.png with the actual Hetzner logo or image? It is currently an image of the AWS logo.

Please give us your thoughts.

erdinchas’s picture

Status: Needs work » Needs review

Thanks for the feedback, the issues you've raised must be solved with the latest push. Hetzner Cloud's field_api_endpoint is renamed as you've suggested. Could you please review again?

yas’s picture

Title: Hetzner Support » Support Hetzner
Status: Needs review » Needs work

@erdinchas

Thank you for the wonderful patch. I posted my comments. Also, please add the PHPUnit tests based on CRUD. We won't merge the source code wi/o tests.

Thanks

baldwinlouie’s picture

@erdinchas Thank you for the updated patch. Again, this is a lot of work and we really appreciate the patch!
I can install it now and am testing it out. I have some requests for you.

1. Please add the Hetzner Cloud provider menu to the left hand navigation. If you look at aws_cloud.links.menu.yml, you will see a snippet that looks like this.

aws_cloud.menu.cloud_context:
  deriver: 'Drupal\aws_cloud\Plugin\Derivative\AwsCloudMenuLinks'
  weight: 100

This will dynamically add the AWS provider to the left hand navigation. See the following two images for reference. Notice the AWS links shows up in to submenus.


2. In the Add Hetzner Cloud screen, can you put the fields in #type=>details, similar to AWS, K8s? See screenshot:

Please look at the code in aws_cloud_form_cloud_config_aws_cloud_form_common_alter(), specifically, the function aws_cloud_cloud_config_fieldsets(), which orders the fields for aws.

You will see similar functions in K8s.
k8s_form_cloud_config_k8s_form_common_alter()
k8s_cloud_config_fieldsets()

3. I'd like to ask that #type=>details be used for the Hetzner entities' add/edit forms. See screenshot for an example with AWS.

You can look at the Add/Edit forms in aws_cloud/src/Form/Ec2/. All the forms are nested in '#type' => 'details',

I will sign up for an account and do some additional testing.

erdinchas’s picture

Thank you both for your to the point fedbacks, appreciated!
I am just back from KubeCon and will work on your recent comments as soon as I can.

erdinchas’s picture

@yas I have completed the revisions you've mentioned; those are ready to be reviewed.

@baldwinlouie now I'm starting to work on your suggestions :)

erdinchas’s picture

Status: Needs work » Needs review

@baldwinlouie, can you review?

Thanks to both of you for your guidance, looking forward to hear about what comes next; cheers!

erdinchas’s picture

I just received a warning about pipeline failure, is the pipeline up to date or should I update the code?
Have a nice weekend!

baldwinlouie’s picture

@erdinchas, Thank you for the updated patch. I will review it.

Don't worry about the pipeline failure. This is happening with our other Merge Requests as well.

baldwinlouie’s picture

Status: Needs review » Needs work

@erdinchas, I'm beginning to test the MR. The first issue I ran into is the following error:

NOTICE: PHP message: Uncaught PHP Exception Drupal\Component\Plugin\Exception\PluginException: "cloud_context property is required for (hetzner_cloud.cloud_config:1)" at /var/www/html/docroot/modules/contrib/cloud/src/Plugin/cloud/config/CloudConfigPluginManager.php line 109
[Thu Apr 23 16:42:40.480976 2026] [proxy_fcgi:error] [pid 1952:tid 1985] [client 172.18.0.5:50910] AH01071: Got error 'PHP message: Uncaught PHP Exception Drupal\\Component\\Plugin\\Exception\\PluginException: "cloud_context property is required for (hetzner_cloud.cloud_config:1)" at /var/www/html/docroot/modules/contrib/cloud/src/Plugin/cloud/config/CloudConfigPluginManager.php line 109'

When this happens, the module will return an error and the entire site will display a blank screen.

I've looked at the patch and traced it back to the following function.

function hetzner_cloud_form_cloud_config_hetzner_cloud_add_form_alter(array &$form, FormStateInterface $form_state, $form_id): void {
  $form['cloud_context']['#access'] = FALSE;
  hetzner_cloud_form_cloud_config_hetzner_cloud_form_common_alter($form, $form_state, $form_id);
}

$form['cloud_context']['#access'] = FALSE;

When this is done, the `cloud_context` is not set. As a result, it is not set in the database:

+----+-----+---------------+----------+-----+---------------------+------------------+------------+--------------+--------------+---------------+---------------+--------+------------+------------+-------------------------------+------------------+
| id | vid | type          | langcode | uid | name                | image__target_id | image__alt | image__title | image__width | image__height | cloud_context | status | created    | changed    | revision_translation_affected | default_langcode |
+----+-----+---------------+----------+-----+---------------------+------------------+------------+--------------+--------------+---------------+---------------+--------+------------+------------+-------------------------------+------------------+
|  1 |   1 | aws_cloud     | en       |   1 | aws (N. California) |             NULL | NULL       | NULL         |         NULL |          NULL | aws_us_west_1 |      1 | 1776988021 | 1776988021 |                             1 |                1 |
|  2 |   2 | hetzner_cloud | en       |   1 | hetzner 1           |             NULL | NULL       | NULL         |         NULL |          NULL |     |      1 | 1776988492 | 1776988492 |                             1 |                1 |
+----+-----+---------------+----------+-----+---------------------+------------------+------------+--------------+--------------+---------------+---------------+--------+------------+------------+-------------------------------+------------------+
2 rows in set (0.006 sec)

As you know from building the Hetzner module, everything in the `cloud` suite is driven by the `cloud_context` value. All urls, plugin, look ups..etc...use that value.

I'm sure you're basing the $form['cloud_context']['#access'] = FALSE; by looking at the other `provider modules` in the Cloud suite.

Yes, we set it to FALSE, but we also use a custom `submit` function, and in it, we use a service to auto-generate the `cloud_context`.

For example, take a look at k8s_form_cloud_config_k8s_add_form_submit

We set `cloud_context` to FALSE, but we also have a custom submit function to handle auto-generating the cloud_context value

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);
}

Code in the K8s module that handles generating the cloud_context

function k8s_form_cloud_config_k8s_add_form_submit(array $form, FormStateInterface $form_state): void {
  // Create CloudConfig.
  $entity = $form_state->getFormObject()->buildEntity($form, $form_state);

  // Set cloud_context.
  $name = $form_state->getValue('name')[0]['value'];
  $cloud_context = \Drupal::service('cloud')->generateCloudContext($name);
  $entity->setCloudContext($cloud_context);
  $entity->save();

Other examples include the vmware_form_cloud_config_vmware_add_form_alter and vmware_form_cloud_config_vmware_add_form_submit functions.

Can I ask you to update the the hetzner_cloud module to follow this convention?

Btw, is there a trial key for Hetzner cloud? I'd like to give the module a try too.

yas’s picture

erdinchas’s picture

Status: Needs work » Needs review

I've tried my best to fix the mentioned issues, struggled a bit, but I think they're solved as far as I see.
Thanks again for your feedbacks.

orkutmuratyilmaz’s picture

Status: Needs review » Needs work

@erdinchas, sorry, but you need to pull the latest release of 8.x-dev first. then you should push your changes into this MR. I'm updating the issue's status.

yas’s picture

@orkutmuratyilmaz

Thank you for the follow-up.

@erdinchas More specifically, please try the following:

git checkout 8.x
git pull origin 8.x
git checkout 3576337-hetzner-support
git merge 8.x
git push --set-upstream cloud-3576337
erdinchas’s picture

Status: Needs work » Needs review

I finished merging the latest commits on 8.x and pushed it as suggested; however phpunit tests are failing due to PHP version issues.
Could you please check again?

yas’s picture

Status: Needs review » Needs work

@erdinchas

Thank you for the update. Since I've fixed the Drupal CI failure w/ the following issues, so could you please update your MR? Thanks!

erdinchas’s picture

Status: Needs work » Needs review
baldwinlouie’s picture

Status: Needs review » Needs work
StatusFileSize
new119.76 KB

@erdinchas

Thank you for the update. I can now add a Hetzner cloud service provider without any errors.

Can I ask you to implement HetznerCloudLocalTasks.php? Please look at K8sLocalTasks.php and AwsCloudLocalTasks.php for reference.

When implemented, all the Hetzner view's pages will have links to the other Hetzner entities. See following screenshot as an example.

erdinchas’s picture

Status: Needs work » Needs review

Thank you for your feedback. The Hetzner Cloud entities include Servers, Volumes, Networks, Firewalls, Floating IPs, SSH Keys, Images, and Load Balancers. I have added them, ensuring the implementation style remains consistent with the existing AWS entities.

baldwinlouie’s picture

Status: Needs review » Needs work
StatusFileSize
new220.53 KB

@erdinchas Thank you so much for the update. The links now appear and I can click between the different Hetzner entities.

I did find a bug. Steps to reproduce.

1. Navigate to any of the Hetzner Cloud entity listing page
2. Click on "Local balancers"
3. Click "Add Load Balancer"

When doing so, I get the following exception:

The website encountered an unexpected error. Try again later.

Error: Call to a member function loadCredentials() on null in Drupal\cloud\Plugin\cloud\config\CloudConfigPluginManager->loadCredentials() (line 221 of modules/contrib/cloud/src/Plugin/cloud/config/CloudConfigPluginManager.php).
Drupal\hetzner_cloud\Service\HetznerCloudService->getClient() (Line: 861)
Drupal\hetzner_cloud\Service\HetznerCloudService->getLoadBalancerTypes() (Line: 72)
Drupal\hetzner_cloud\Form\HetznerCloudLoadBalancerCreateForm->buildForm()
call_user_func_array() (Line: 554)
Drupal\Core\Form\FormBuilder->retrieveForm() (Line: 303)
Drupal\Core\Form\FormBuilder->buildForm() (Line: 73)
Drupal\Core\Controller\FormController->getContentResult()
call_user_func_array() (Line: 123)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->{closure:Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber::wrapControllerExecutionInRenderContext():121}() (Line: 634)
Drupal\Core\Render\Renderer::{closure:Drupal\Core\Render\Renderer::executeInRenderContext():634}()
Fiber->start() (Line: 635)
Drupal\Core\Render\Renderer->executeInRenderContext() (Line: 121)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext() (Line: 97)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->{closure:Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber::onController():96}() (Line: 183)
Symfony\Component\HttpKernel\HttpKernel->handleRaw() (Line: 76)
Symfony\Component\HttpKernel\HttpKernel->handle() (Line: 54)
Drupal\simple_oauth\HttpMiddleware\BasicAuthSwap->handle() (Line: 53)
Drupal\Core\StackMiddleware\Session->handle() (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle() (Line: 28)
Drupal\Core\StackMiddleware\ContentLength->handle() (Line: 118)
Drupal\page_cache\StackMiddleware\PageCache->pass() (Line: 92)
Drupal\page_cache\StackMiddleware\PageCache->handle() (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle() (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle() (Line: 53)
Drupal\Core\StackMiddleware\AjaxPageState->handle() (Line: 54)
Drupal\Core\StackMiddleware\StackedHttpKernel->handle() (Line: 745)
Drupal\Core\DrupalKernel->handle() (Line: 19)

Can you please take a look?

baldwinlouie’s picture

StatusFileSize
new237.08 KB

@erdinchas I get a similar error when trying to add an ssh key. It seems to be happening when I try to add any of the Hetzner entities.

1. Navigate to any of the Hetzner SSH keys listing page: /clouds/hetzner_cloud/hetzner/ssh_key
2. Click "Add SSH Key"
3. Fill in the SSH key "Name" field
4. Click Save

I get the following error:

The website encountered an unexpected error. Try again later.

Drupal\cloud\Plugin\cloud\config\CloudConfigPluginException: Cannot load cloud service provider plugin: <em class="placeholder">&lt;Empty&gt;</em> ($cloud_context) at <em class="placeholder">Drupal\cloud\Plugin\cloud\config\CloudConfigPluginManager::setCloudContext()</em> in Drupal\cloud\Plugin\cloud\config\CloudConfigPluginManager->setCloudContext() (line 130 of modules/contrib/cloud/src/Plugin/cloud/config/CloudConfigPluginManager.php).
Drupal\cloud\Form\CloudContentForm->save() (Line: 41)
Drupal\hetzner_cloud\Form\HetznerCloudSshKeyEditForm->save() (Line: 108)
Drupal\Core\Form\FormSubmitter->executeSubmitHandlers() (Line: 45)
Drupal\Core\Form\FormSubmitter->doSubmitForm() (Line: 615)
Drupal\Core\Form\FormBuilder->processForm() (Line: 347)
Drupal\Core\Form\FormBuilder->buildForm() (Line: 73)
Drupal\Core\Controller\FormController->getContentResult()
call_user_func_array() (Line: 123)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->{closure:Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber::wrapControllerExecutionInRenderContext():121}() (Line: 634)
Drupal\Core\Render\Renderer::{closure:Drupal\Core\Render\Renderer::executeInRenderContext():634}()
Fiber->start() (Line: 635)
Drupal\Core\Render\Renderer->executeInRenderContext() (Line: 121)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext() (Line: 97)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->{closure:Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber::onController():96}() (Line: 183)
Symfony\Component\HttpKernel\HttpKernel->handleRaw() (Line: 76)
Symfony\Component\HttpKernel\HttpKernel->handle() (Line: 54)
Drupal\simple_oauth\HttpMiddleware\BasicAuthSwap->handle() (Line: 53)
Drupal\Core\StackMiddleware\Session->handle() (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle() (Line: 28)
Drupal\Core\StackMiddleware\ContentLength->handle() (Line: 118)
Drupal\page_cache\StackMiddleware\PageCache->pass() (Line: 92)
Drupal\page_cache\StackMiddleware\PageCache->handle() (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle() (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle() (Line: 53)
Drupal\Core\StackMiddleware\AjaxPageState->handle() (Line: 54)
Drupal\Core\StackMiddleware\StackedHttpKernel->handle() (Line: 745)
Drupal\Core\DrupalKernel->handle() (Line: 19)

baldwinlouie’s picture

StatusFileSize
new421.04 KB

@erdinchas Please take a look at one of the *CreateForm.php for AWS. The issue is because for cloud_context is not being passed to the save() function.

The {cloud_context} can be accessed as a method parameter for buildForm(). For example, in ElasticIpCreateForm.php

public function buildForm(array $form, FormStateInterface $form_state, $cloud_context = ''): array {
    /** @var \Drupal\aws_cloud\Entity\Ec2\ElasticIp $entity */
    $form = parent::buildForm($form, $form_state);

    $this->ec2Service->setCloudContext($cloud_context);

If I do the same for HetznerCloudNetworkCreateForm.php

  public function buildForm(array $form, FormStateInterface $form_state, $cloud_context = ''): array {
    $form = parent::buildForm($form, $form_state);

I am able to pick up the $cloud_context. See screenshot of my PHPStorm debug session.

baldwinlouie’s picture

@erdinchas I'm sorry to be asking you so many questions. I have another one.

I am looking at all the HetznerCloud*EditForms. It looks you are just making the edits in Drupal, and not making an API call to Hetzner to update the remote entities? For example, in HetznerCloudNetworkEditForm, the save() function looks like this:

public function save(array $form, FormStateInterface $form_state): void {
    parent::save($form, $form_state);
    $form_state->setRedirect(
      'entity.hetzner_cloud_network.canonical',
      [
        'cloud_context' => $this->entity->getCloudContext(),
        'hetzner_cloud_network' => $this->entity->id(),
      ]
    );
  }

Can you confirm if the remote entity is updated?

erdinchas’s picture

Status: Needs work » Needs review

I am so glad about your questions and thanks for the detailed entries. They helped me to see a foundational problem that I've missed when making $cloud_context edits.

The exceptions on Add Load Balancer and Add SSH Key were both coming from the same place; our Create/Edit forms weren't accepting $cloud_context as a buildForm() parameter, so the entity hit save() with an empty cloud context and CloudConfigPluginManager::setCloudContext() blew up. I aligned all eight Hetzner Create/Edit forms (buildForm(..., $cloud_context = '')), set cloud_context on new entities before save.

On the last comment — yes, edits were only writing to Drupal locally and never reaching Hetzner. I have actually tested them with already existing entites, added and deleted them somehow, saw the actions took place on Hetzner console and tought it was working. Now I added updateServer/Volume/Network/Firewall/FloatingIp/SshKey/Image/LoadBalancer to HetznerCloudServiceInterface (wrapping the SDK's ->update() on each model) and wired every *EditForm::save() to push the change remotely first, aborting the local save if the API call fails.

For the Server form specifically, I followed the AWS convention: name is editable, server_type is editable but disabled unless the server is off (calling resizeServer() with an optional disk-upgrade checkbox), and image stays read-only since changing it is a rebuild action, not an update — same as how AWS treats AMI swaps.

Btw as I am unsure if you received my direct message about trial Hetzner API, I want to address it here as well. You can find it below;

"I was going to tell you that Hetzner does not make out an invoice under 5$ bill and there is no fee for account creation, so you can register and test it out freely for a while. Just do not forget to kill the servers after testing for not to get invoiced."

baldwinlouie’s picture

Status: Needs review » Needs work
StatusFileSize
new547.81 KB
new243.18 KB
new282.37 KB

@erdinchas Thank you so much for providing the Hetzner invoicing insight. I've created an account to do testing. It has been going great.

Thank you for fixing the Load Balancer and SSH Key issues. I'm doing more testing and am finding some additional issues.

1. Creating a volume is throwing a PHP exception. Steps to reproduce.
- Go to `Add Hetzner Cloud volume`
- Fill out the form. I filled out the following: Name="Test", Size="10", Location="fsn1", Filesystem Format="ext4"
- After clicking "Save", I get the following error:

 The website encountered an unexpected error. Try again later.

TypeError: LKDev\HetznerCloud\Models\Volumes\Volumes::create(): Argument #2 ($size) must be of type int, string given, called in /var/www/html/docroot/modules/contrib/cloud/modules/cloud_service_providers/hetzner_cloud/src/Service/HetznerCloudService.php on line 442 in LKDev\HetznerCloud\Models\Volumes\Volumes->create() (line 131 of /var/www/html/vendor/lkdevelopment/hetzner-cloud-php-sdk/src/Models/Volumes/Volumes.php).
Drupal\hetzner_cloud\Service\HetznerCloudService->createVolume() (Line: 129)
Drupal\hetzner_cloud\Form\HetznerCloudVolumeCreateForm->save() (Line: 108)
Drupal\Core\Form\FormSubmitter->executeSubmitHandlers() (Line: 45)
Drupal\Core\Form\FormSubmitter->doSubmitForm() (Line: 615)
Drupal\Core\Form\FormBuilder->processForm() (Line: 347)
Drupal\Core\Form\FormBuilder->buildForm() (Line: 73)
Drupal\Core\Controller\FormController->getContentResult()
call_user_func_array() (Line: 123)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->{closure:Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber::wrapControllerExecutionInRenderContext():121}() (Line: 634)
Drupal\Core\Render\Renderer::{closure:Drupal\Core\Render\Renderer::executeInRenderContext():634}()
Fiber->resume() (Line: 649)
Drupal\Core\Render\Renderer->executeInRenderContext() (Line: 121)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext() (Line: 97)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->{closure:Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber::onController():96}() (Line: 183)
Symfony\Component\HttpKernel\HttpKernel->handleRaw() (Line: 76)
Symfony\Component\HttpKernel\HttpKernel->handle() (Line: 54)
Drupal\simple_oauth\HttpMiddleware\BasicAuthSwap->handle() (Line: 53)
Drupal\Core\StackMiddleware\Session->handle() (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle() (Line: 28)
Drupal\Core\StackMiddleware\ContentLength->handle() (Line: 118)
Drupal\page_cache\StackMiddleware\PageCache->pass() (Line: 92)
Drupal\page_cache\StackMiddleware\PageCache->handle() (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle() (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle() (Line: 53)
Drupal\Core\StackMiddleware\AjaxPageState->handle() (Line: 54)
Drupal\Core\StackMiddleware\StackedHttpKernel->handle() (Line: 745)
Drupal\Core\DrupalKernel->handle() (Line: 19)

2. Creating an SSH key does not create it on the Hetzner side. Steps to reproduce:
- Go to `Hetzner Cloud SSH Keys`
- Click `Add SSH Key`
- Fill out the Name field
- Click Save.
- Check the Hetzner console. I notice the SSH key is not created.
- I checked HetznerCloudSshKeyCreateForm and the parent save() method. It doesn't seem to be support creating a new SSH key.

3. Creating Floating IP does not create it on the Hetzner side.
Steps to reproduce are the same as #2 above.

4. Creating a Load Balancer keeps producing a Name field is required. validation error. Steps to reproduce:
- Go to `Hetzner Cloud Load Balancers`
- Click `Add Load Balancer`
- Fill out the form. I made sure to fill out the `Name` at the top and bottom of the form.
- I get the following screenshot:

5. Creating a Server brings back the following error (Please advise if this is user error)

Failed to create server: Client error: `POST https://api.hetzner.cloud/v1/servers` resulted in a `422 Unprocessable Entity` response: { "error": { "code": "invalid_input", "message": "unsupported location for server type", "details": {} } }

Steps to reproduce:
- Fill out the `Add Hetzner server` form
- Server Name: Test
- Location: Either `Auto select` or fsn1
- Server Type: cpx11
- Image: Debian 11
- Check my default SSH key
- After clicking save. I get this screenshot. I also notice the form does not repopulate with my previous entries.

Can you please take a look at these?

Thank you again in advance.

erdinchas’s picture

Status: Needs work » Needs review

Hi @baldwinlouie,

Thanks a lot for the thorough review and for taking the time to test against a real Hetzner account — really appreciated. I've pushed fixes for all five issues you pointed:

1. Volume creation — the service was passing arguments to the SDK in the wrong order (size, name, …) when the SDK expects (name, size, server, location, …). Location also had to be resolved to a Location object rather than passed as a string.

2. SSH keys — the create form was missing a public_key field entirely, and save() only persisted locally. Added the field and rewired save() to call createSshKey() on the API, then re-sync.

3. Floating IPs — same root cause as SSH keys (form missing type, home_location, description; save() never hitting the API), plus the service's createFloatingIp() had its arguments in the wrong order.

4. Load Balancer name validation — the name base field had setDisplayOptions('form', …), so Drupal auto-rendered a widget that collided with the custom name field. Also discovered the SDK's LoadBalancers class doesn't expose a create() method at all, so the service now POSTs to /load_balancers directly via the HTTP client. While testing the fix I hit a follow-up "Cannot unset string offsets" fatal — that's also resolved by removing the name component from the entity form display in buildForm().

5. Server creation — made location required (the "Auto select" path was letting Hetzner pick a location that didn't support the chosen server type), added validation when the submitted server_type isn't in the catalog, and switched the error path to setRebuild(TRUE) so user input is preserved on API errors. Applied the same retention pattern to the Volume form.

Could you give it another go when you get a chance?