Skip to content

Cannot deselect an assigned IP pool from the server edit form #3562

@vaibhavheda

Description

@vaibhavheda

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:

# 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/<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:

bin/rails runner 'Server.find_by(permalink: "<server>").update(ip_pool_id: nil); \
  puts Server.find_by(permalink: "<server>").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/<org>/servers/<server>/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:

= 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 <option> 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: <PASTE_PR_LINK_ONCE_OPENED>

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions