Skip to content

instance_pool: Add support for private instance pools#492

Open
francoismeulenberg wants to merge 6 commits into
exoscale:masterfrom
francoismeulenberg:master
Open

instance_pool: Add support for private instance pools#492
francoismeulenberg wants to merge 6 commits into
exoscale:masterfrom
francoismeulenberg:master

Conversation

@francoismeulenberg

Copy link
Copy Markdown

Description

instance_pool: Add support for private instance pools (no public IP addresses)

Changes

Implementation

  • Added AttrPrivate schema attribute with ForceNew: true (private pools cannot be converted to public or vice versa)
  • Implemented mapping between private/ipv6 attributes and the API's PublicIPAssignment field:
    • private = truePublicIPAssignment = "none" (no public IP)
    • ipv6 = truePublicIPAssignment = "dual" (IPv4 + IPv6)
    • Default → PublicIPAssignment = "inet4" (IPv4 only)
  • Added mutual exclusivity constraint: private and ipv6 cannot both be enabled (ConflictsWith validation)
  • Updated create, read, and update functions to handle the new PublicIPAssignment logic
  • Generated documentation reflecting the new attribute

Comments for This PR

On rCreate() PublicIPAssignment logic:

Mapping between Terraform attributes and API v3 PublicIPAssignment field.
This matches the pattern used in the exoscale_instance resource.

On AttrPrivate schema:

ForceNew: true because API doesn't allow changing public IP assignment after pool creation.
ConflictsWith ipv6 since they map to different PublicIPAssignment values.

On rUpdate() IPv6 handling:

Only update PublicIPAssignment if not private, since private pools have
PublicIPAssignment="none" which cannot be changed (ForceNew).

On testResourcePrivate() cloud-init:

Cloud-init configures netplan for eth1 (private network interface) with DHCP.
Required for private instances to get an IP and successfully boot.

Checklist

(For exoscale contributors)

  • Changelog updated (under Unreleased block)
  • Acceptance tests OK
  • For a new resource, datasource or new attributes: acceptance test added/updated

Testing

  • Added testResourcePrivate() acceptance test that:
    • Creates a private instance pool with a private network attachment
    • Includes cloud-init configuration to set up DHCP on the private network interface (eth1)
    • Verifies PublicIPAssignment = "none" is set correctly in the API
    • Validates instances boot successfully and pool reaches healthy state
  • All tests pass successfully:
    • TestInstancePool/ResourcePrivate
    • Original TestInstancePool/Resource remains unchanged and passing
➜  terraform-provider-exoscale git:(make-instance-pools-private) ✗ TF_ACC=1 go test -v -timeout=90m -tags=testacc ./pkg/resources/instance_pool/                                      
=== RUN   TestInstancePool
=== RUN   TestInstancePool/DataSource
=== RUN   TestInstancePool/DataSourceList
=== RUN   TestInstancePool/Resource
=== RUN   TestInstancePool/ResourcePrivate
--- PASS: TestInstancePool (242.19s)
    --- PASS: TestInstancePool/DataSource (59.27s)
    --- PASS: TestInstancePool/DataSourceList (38.75s)
    --- PASS: TestInstancePool/Resource (96.63s)
    --- PASS: TestInstancePool/ResourcePrivate (47.54s)
PASS
ok      github.com/exoscale/terraform-provider-exoscale/pkg/resources/instance_pool     242.216s

@francoismeulenberg

Copy link
Copy Markdown
Author

Background information:
For my current Exoscale project I need to be able to create private instance pools.
I noticed that this option was not yet available in the Exoscale provider for instance pools.

So I have added it.

@pierre-emmanuelJ pierre-emmanuelJ requested review from a team and pierre-emmanuelJ February 2, 2026 15:20
Comment on lines +362 to +386
// Handle public IP assignment based on private and ipv6 settings
if privateInstance, ok := d.GetOk(AttrPrivate); ok {
privateInstanceBool := privateInstance.(bool)
if privateInstanceBool {
createPoolRequest.PublicIPAssignment = v3.CreateInstancePoolRequestPublicIPAssignmentNone
} else if enableIPv6, ok := d.GetOk(AttrIPv6); ok {
ipv6EnabledBool := enableIPv6.(bool)
if ipv6EnabledBool {
createPoolRequest.PublicIPAssignment = v3.CreateInstancePoolRequestPublicIPAssignmentDual
} else {
createPoolRequest.PublicIPAssignment = v3.CreateInstancePoolRequestPublicIPAssignmentInet4
}
} else {
createPoolRequest.PublicIPAssignment = v3.CreateInstancePoolRequestPublicIPAssignmentInet4
}
} else if enableIPv6, ok := d.GetOk(AttrIPv6); ok {
ipv6EnabledBool := enableIPv6.(bool)
if ipv6EnabledBool {
createPoolRequest.PublicIPAssignment = v3.CreateInstancePoolRequestPublicIPAssignmentDual
} else {
createPoolRequest.PublicIPAssignment = v3.CreateInstancePoolRequestPublicIPAssignmentInet4
}
} else {
createPoolRequest.PublicIPAssignment = v3.CreateInstancePoolRequestPublicIPAssignmentInet4
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you simplify this block with switch please?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hint: you can default to ipv4.

Comment on lines +751 to +772
if pool.PublicIPAssignment == v3.PublicIPAssignmentNone {
if err := d.Set(AttrPrivate, true); err != nil {
return diag.FromErr(err)
}
if err := d.Set(AttrIPv6, false); err != nil {
return diag.FromErr(err)
}
} else if pool.PublicIPAssignment == v3.PublicIPAssignmentDual {
if err := d.Set(AttrPrivate, false); err != nil {
return diag.FromErr(err)
}
if err := d.Set(AttrIPv6, true); err != nil {
return diag.FromErr(err)
}
} else {
// inet4 or empty
if err := d.Set(AttrPrivate, false); err != nil {
return diag.FromErr(err)
}
if err := d.Set(AttrIPv6, false); err != nil {
return diag.FromErr(err)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: I would prefer if you:

  • use a switch here
  • use only once the d.Set(AttrPrivate,...) and d.Set(AttrIPv6, ...), it will keep the code easy to read (you can use variables in your switch case and set variable in the state ouside of the switch)

data "exoscale_template" "debian" {
zone = local.zone
name = "Linux Debian 12 (Bookworm) 64-bit"
name = "Linux Ubuntu 24.04 LTS 64-bit"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick but resource name (2 lines above) is debian.

- [ netplan, apply ]
EOT

depends_on = [exoscale_private_network.test_private]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should not be needed

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

terraform will make it depend due to reference in network_ids

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants