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?

baldwinlouie’s picture

@erdinchas Thank you for the updated patch. We are getting close!

Volume reate/delete - Works now!

SSH Keys - I'm getting the following error when trying to add SSH Keys

Floating IPs create/delete - Works now!

Load Balancer create/delete - Works now! There is a UI issue I was hoping you can adjust. The Revision log message can be hidden. It isn't something that needs to be displayed

Servers create/delete - Works now!. However, there was a combo that was still giving me "unsupported location for server type" error. Please check this one out?
- fsn1
- cpx11
- Debian 11

I have some low priority UI issues. Please see if these are things you can take a look at too.

On some of the add/edit forms (Server, Volumes and Network), the Authored By is displayed at the bottom outside of a fieldset. In our AWS implementation, we put any additional fields in a collapsed fieldset called "Others". I was wondering if you might want to do the same for your forms. It will help give it a uniform look with the other cloud service providers. See screenshot.

baldwinlouie’s picture

Status: Needs review » Needs work
erdinchas’s picture

Status: Needs work » Needs review

@baldwinlouie
I've pushed fixes for the things you flagged:

1. SSH Key add error — the form was reading the name from the (unpopulated) entity instead of form state. The custom name/public_key fields now have to be binding correctly via #parents, so creation should be going through.
2. Load Balancer "Revision log message" — hidden on all Hetzner create/edit forms via a shared hideRevisionFields() helper (Hetzner entities aren't actually revisioned, so this widget was just leaking in from the parent base fields).
3. fsn1 + cpx11 + Debian 11 "unsupported location" — realized that it was because Hetzner keeps deprecated server types (like cpx11) and deprecated images (like Debian 11) in the catalog but rejects them on new orders with that misleading error. Both the server-type dropdown and the image dropdown now filter out anything with a deprecated/deprecation flag, and validateForm() mirrors the checks so stale form state can't slip through.
4. "Authored By" outside fieldsets — every Hetzner create/edit form now follows the AWS pattern and renders the meta fields inside a collapsed "Others" fieldset (Server, Volume, Network, Load Balancer, Floating IP, Firewall, SSH Key, Image).

Hopefully ready for another go whenever you have time

baldwinlouie’s picture

StatusFileSize
new165.99 KB

@erdinchas Thank you for the updated patch. Sorry, this has taken so long.

I've tested the fixes. The only one I am still having issues with is #3.

I tried fsn1 + cpx11 + Debian 11. The message now says:

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": {} } }

Can you please take a look at this?

erdinchas’s picture

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

I just created an issue on Hetzner-php-sdk repo.

I am debugging with a workaround, and will need a re-work when the sdk has a fix for it.

I'll push it when it seems to be working.

erdinchas’s picture

Hi,

My MR fails the pipeline because of the exact reason of this issue.
https://www.drupal.org/project/cloud/issues/3594984

Unfortunately, I am unable to create a healthy MR and so won't change the status to 'Needs Review"

Wanted to bring it to your attention.

Cheers.

yas’s picture

@erdinchas

Thank you for letting us know about it. I've fixed it at #3594984: Fix incorrect route provider return types from ?\Serializable to ?Route so I hope that you can proceed with creating your MR.

HTH

erdinchas’s picture

Status: Needs work » Needs review

The new lkdevelopment/hetzner-cloud-php-sdk v3.1.0 release maps the deprecated/deprecation fields on server types (issue #151 / PR #152 upstream), so I've bumped the constraint to ^3.1 and reverted our raw-JSON workaround — getServerTypes() is back to the normal SDK call now.

yas’s picture

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

@erdinchas

I'm truly sorry for keeping you waiting for so long after you took the time to update the MR. Some personal circumstances delayed my review.

I reviewed it and found a bug. I will update your MR and follow up in my next comment.

yas’s picture

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

@erdinchas

I've updated your MR. Could you please review the changes?

orkutmuratyilmaz’s picture

This MR needs a rebase too:) @erdinchas

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

ai_yas’s picture

@orkutmuratyilmaz @erdinchas

MR !2348 has been rebased onto the latest 8.x, and the merged-results pipeline passed. Could you please review it?

orkutmuratyilmaz’s picture

Status: Needs review » Reviewed & tested by the community

I've just completed the reviewing.

I'll have some more feature requests about Hetzner, but this issue looks like completed. It does basic CRUD operations for Hetzner services very well:)

@erdinchas, @yas, @baldwinlouie and @ai_yas, thanks for everything you've done:) This process taught me a lot.

Have a wonderful September:)

yas’s picture

I’ll merge the MR into 8.x and 9.x; and close this issue as Fixed.

  • ai_yas committed 22e644d9 on 8.x authored by erdinchas
    feat: #3576337 Add Hetzner Cloud as a cloud service provider
    
yas’s picture

Title: Support Hetzner » Add Hetzner Cloud as a cloud service provider

  • ai_yas committed 95e64f74 on 9.x
    feat: #3576337 Add Hetzner Cloud as a cloud service provider
    
yas’s picture

Status: Reviewed & tested by the community » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

ai_yas changed the visibility of the branch 3576337-d12-sdk-trial to hidden.