postalserver / postalserver/postal
Cannot deselect an assigned IP pool from the server edit form
- Dominant language
- Ruby
- Stars
- 16.8k
- Forks
- 1.3k
- Avg merge
- 13d 7h
- Merged PRs (30d)
- 2
Description
Once an IP pool has been assigned to a server, there is no way to clear (unset) it from the web UI. The IP-pool dropdown on the server-edit form lists only existing pools — there is no "(none)" / blank option. Saving the form re-persists whatever pool is already selected, so `servers.ip_pool_id` cannot become `NULL` through the UI.
The `Server` model itself accepts a nil `ip_pool_id`:
```ruby
# app/models/server.rb
belongs_to :ip_pool, optional: true
def validate_ip_pool_belongs_to_organization
return unless ip_pool && ip_pool_id_changed? && !organization.ip_pools.include?(ip_pool)
errors.add :ip_pool_id, "must belong to the organization"
end
```
…and the controller's strong parameters already permit `:ip_pool_id`. Only the view stands in the way of an unset.
## To Reproduce
Prerequisites: `postal.use_ip_pools: true` in `postal.yml`; an organization with at least one IP pool assigned.
1. Sign in as an admin user.
2. Go to **Admin → IP Pools** and create a pool (e.g. `Test Pool`); add at least one IP address to it.
3. Assign the pool to an organization at `/org//ip_pools`.
4. Inside that organization, create a new mail server (or edit an existing one). On the server form, the IP Pool dropdown lists `Test Pool`. Pick it and save.
5. Click **Edit** on that server again.
6. Try to clear the IP pool. There is no blank/none option in the dropdown — the only options are the existing pools.
7. No matter what you do, you cannot save the server with `ip_pool_id = NULL`.
Verifying via Rails console makes the symptom unambiguous:
```bash
bin/rails runner 'Server.find_by(permalink: "").update(ip_pool_id: nil); \
puts Server.find_by(permalink: "").ip_pool_id.inspect'
# → nil (the model accepts the change)
```
But the same change is impossible through the UI.
## Expected behaviour
The IP-pool dropdown on the server-edit form should include a blank/"no pool" option (e.g. *"No specific pool (use rules / default)"*) so that an operator can set `servers.ip_pool_id = NULL`. Saving with the blank option selected should persist as nil and survive a page reload.
(Same minor concern on the IP-Pool-Rule form at `/org//servers//ip_pool_rules/new` — the dropdown auto-selects the first pool with no placeholder. A `prompt:` would make the form's intent clearer for new rules. However, since `IPPoolRule#ip_pool` is required, this should be a `prompt:` rather than `include_blank:`.)
## Environment details
- OS: any (reproduced on Ubuntu 22.04 + Docker, and on macOS dev environment)
- Browser: any (reproduced in Chrome and Safari)
- Postal version: 3.3.6 (also present on `main` at time of report)
- Type: web admin UI
## Additional information/context
### Root cause
`app/views/servers/_form.html.haml` line 28:
```haml
= f.collection_select :ip_pool_id,
organization.ip_pools.includes(:ip_addresses).order("`default` desc, name asc"),
:id, :name,
{}, # ← empty options hash, no include_blank / no prompt
:class => 'input input--select'
```
The 5th argument is the options hash. With nothing in it, Rails' `collection_select` renders only the rows from the collection — no blank `` is emitted, and the browser submits whichever pool is currently selected.
Same pattern exists on `app/views/ip_pool_rules/_form.html.haml` line 27.
### Why this matters operationally
This bug is most visible in cloud environments where the IPs configured in the pool aren't bindable on the host (e.g. AWS Elastic IPs that aren't on the local NIC; the bind has to target the matching secondary private IP instead). When that happens, `Net::SMTP#source_address = ip_address.ipv4` fails with `EADDRNOTAVAIL`, queued messages stop sending, and the operator's natural recovery — "let me clear the pool from the server until I sort the IP rows out" — is impossible from the UI. Recovery currently requires shell access to a Rails console, which is often not available in production.
### Suggested fix
Add `include_blank: "No specific pool (use rules / default)"` to the server form's `collection_select` and `prompt: "Select an IP pool"` to the rule form's `collection_select`.
A PR with exactly that change is up at:
Contributor guide
Assessment
This issue has not been assessed yet.