Skip to content

Commit 4a3649c

Browse files
committed
oxide_ip_pool_silo_link: use composite attributes for id
The `id` attribute for the `oxide_ip_pool_silo_link` resource now uses the format `IP_POOL_ID/SILO_ID`, matching the `oxide_subnet_pool_silo_link` resource. Closes SSE-348.
1 parent 581a46a commit 4a3649c

9 files changed

Lines changed: 183 additions & 200 deletions

File tree

.changelog/0.21.0.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
[[breaking]]
22

33
[[features]]
4+
title = "`oxide_ip_pool_silo_link`"
5+
description = "The `id` attribute for the `oxide_ip_pool_silo_link` resource now uses the format `IP_POOL_ID/SILO_ID`, matching the `oxide_subnet_pool_silo_link` resource. [#794](https://github.com/oxidecomputer/terraform-provider-oxide/pull/794)"
46

57
[[enhancements]]
68

docs/resources/ip_pool_silo_link.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,5 +60,6 @@ Import is supported using the following syntax:
6060
The [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import) can be used, for example:
6161

6262
```shell
63-
terraform import oxide_ip_pool_silo_link.example 3e2c6e84-bed8-4c94-afc3-1032082d6a90
63+
# Import ID is the format `${IP_POOL_ID}/${SILO_ID}`.
64+
terraform import oxide_ip_pool_silo_link.example 3e2c6e84-bed8-4c94-afc3-1032082d6a90/9e199e45-01a6-43d3-8bc3-5b27726e67a6
6465
```

docs/resources/subnet_pool_silo_link.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,15 @@ description: |-
1010

1111
This resource manages subnet pool to silo links.
1212

13+
## Example Usage
1314

15+
```terraform
16+
resource "oxide_subnet_pool_silo_link" "example" {
17+
silo_id = "1fec2c21-cf22-40d8-9ebd-e5b57ebec80f"
18+
ip_pool_id = "081a331d-5ee4-4a23-ac8b-328af5e15cdc"
19+
is_default = true
20+
}
21+
```
1422

1523
<!-- schema generated by tfplugindocs -->
1624
## Schema
@@ -38,3 +46,14 @@ Optional:
3846
- `delete` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). Setting a timeout for a Delete operation is only applicable if changes are saved into state before the destroy operation occurs.
3947
- `read` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). Read operations occur during any refresh or planning operation when refresh is enabled.
4048
- `update` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours).
49+
50+
## Import
51+
52+
Import is supported using the following syntax:
53+
54+
The [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import) can be used, for example:
55+
56+
```shell
57+
# Import ID is the format `${SUBNET_POOL_ID}/${SILO_ID}`.
58+
terraform import oxide_subnet_pool_silo_link.example 3e2c6e84-bed8-4c94-afc3-1032082d6a90/9e199e45-01a6-43d3-8bc3-5b27726e67a6
59+
```
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
terraform import oxide_ip_pool_silo_link.example 3e2c6e84-bed8-4c94-afc3-1032082d6a90
1+
# Import ID is the format `${IP_POOL_ID}/${SILO_ID}`.
2+
terraform import oxide_ip_pool_silo_link.example 3e2c6e84-bed8-4c94-afc3-1032082d6a90/9e199e45-01a6-43d3-8bc3-5b27726e67a6
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Import ID is the format `${SUBNET_POOL_ID}/${SILO_ID}`.
2+
terraform import oxide_subnet_pool_silo_link.example 3e2c6e84-bed8-4c94-afc3-1032082d6a90/9e199e45-01a6-43d3-8bc3-5b27726e67a6
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
resource "oxide_subnet_pool_silo_link" "example" {
2+
silo_id = "1fec2c21-cf22-40d8-9ebd-e5b57ebec80f"
3+
ip_pool_id = "081a331d-5ee4-4a23-ac8b-328af5e15cdc"
4+
is_default = true
5+
}

internal/provider/ip_pool_silo_link/resource.go

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import (
88
"context"
99
"fmt"
1010
"slices"
11+
"strings"
1112

12-
"github.com/google/uuid"
1313
"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
1414
"github.com/hashicorp/terraform-plugin-framework/path"
1515
"github.com/hashicorp/terraform-plugin-framework/resource"
@@ -75,7 +75,20 @@ func (r *Resource) ImportState(
7575
req resource.ImportStateRequest,
7676
resp *resource.ImportStateResponse,
7777
) {
78-
resource.ImportStatePassthroughID(ctx, path.Root("ip_pool_id"), req, resp)
78+
idParts := strings.Split(req.ID, "/")
79+
if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" {
80+
resp.Diagnostics.AddError(
81+
"Invalid Import ID",
82+
fmt.Sprintf("Expected import ID format: ip_pool_id/silo_id, got: %s", req.ID),
83+
)
84+
return
85+
}
86+
87+
// Use the import ID directly as the terraform ID (it's already in the correct format)
88+
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), req.ID)...)
89+
resp.Diagnostics.Append(
90+
resp.State.SetAttribute(ctx, path.Root("ip_pool_id"), idParts[0])...)
91+
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("silo_id"), idParts[1])...)
7992
}
8093

8194
// Schema defines the schema for the resource.
@@ -167,8 +180,8 @@ func (r *Resource) Create(
167180
map[string]any{"success": true},
168181
)
169182

170-
// Set a unique ID for the resource payload
171-
plan.ID = types.StringValue(uuid.New().String())
183+
// Set a deterministic ID based on composite attributes.
184+
plan.ID = types.StringValue(fmt.Sprintf("%s/%s", link.IpPoolId, link.SiloId))
172185

173186
// Save plan into Terraform state
174187
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
@@ -199,13 +212,11 @@ func (r *Resource) Read(
199212
ctx, cancel := context.WithTimeout(ctx, readTimeout)
200213
defer cancel()
201214

202-
params := oxide.SystemIpPoolSiloListParams{
203-
Pool: oxide.NameOrId(state.IPPoolID.ValueString()),
204-
Limit: oxide.NewPointer(1000000000),
205-
SortBy: oxide.IdSortModeIdAscending,
206-
}
207-
208-
links, err := r.client.SystemIpPoolSiloList(ctx, params)
215+
// This `/v1/system/silos/{silo}/ip-pools` API works with non-discoverable silos
216+
// whereas the `/v1/system/ip-pools/{pool}/silos` does not.
217+
pools, err := r.client.SiloIpPoolListAllPages(ctx, oxide.SiloIpPoolListParams{
218+
Silo: oxide.NameOrId(state.SiloID.ValueString()),
219+
})
209220
if err != nil {
210221
if shared.Is404(err) {
211222
// Remove resource from state during a refresh
@@ -220,27 +231,31 @@ func (r *Resource) Read(
220231
}
221232
tflog.Trace(
222233
ctx,
223-
fmt.Sprintf("read IP pool links with ID: %v", state.IPPoolID),
234+
fmt.Sprintf("read IP pool links for silo: %v", state.SiloID.ValueString()),
224235
map[string]any{"success": true},
225236
)
226237

227-
siloID := state.SiloID.ValueString()
238+
ipPoolID := state.IPPoolID.ValueString()
228239
idx := slices.IndexFunc(
229-
links.Items,
230-
func(l oxide.IpPoolSiloLink) bool { return l.SiloId == siloID },
240+
pools,
241+
func(p oxide.SiloIpPool) bool {
242+
// We check for both ID and name equality to ensure resources that mistakenly
243+
// used the IP pool name aren't removed from state.
244+
return p.Id == ipPoolID || p.Name == oxide.Name(ipPoolID)
245+
},
231246
)
232247
if idx < 0 {
233-
resp.Diagnostics.AddError(
234-
"Missing resource",
235-
fmt.Sprintf("Unable to find requested link between IP pool %v and silo %v",
236-
state.IPPoolID.ValueString(), state.SiloID.ValueString()),
237-
)
248+
resp.State.RemoveResource(ctx)
238249
return
239250
}
240251

241-
state.IPPoolID = types.StringValue(links.Items[idx].IpPoolId)
242-
state.IsDefault = types.BoolPointerValue(links.Items[idx].IsDefault)
243-
state.SiloID = types.StringValue(links.Items[idx].SiloId)
252+
// Set a deterministic ID based on composite attributes.
253+
state.ID = types.StringValue(
254+
fmt.Sprintf("%s/%s", pools[idx].Id, state.SiloID.ValueString()),
255+
)
256+
257+
state.IPPoolID = types.StringValue(pools[idx].Id)
258+
state.IsDefault = types.BoolPointerValue(pools[idx].IsDefault)
244259

245260
// Save updated data into Terraform state
246261
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
@@ -300,8 +315,8 @@ func (r *Resource) Update(
300315
map[string]any{"success": true},
301316
)
302317

303-
// This is a terraform-specific ID. We just copy it from the state
304-
plan.ID = state.ID
318+
// Set a deterministic ID based on composite attributes.
319+
plan.ID = types.StringValue(fmt.Sprintf("%s/%s", link.IpPoolId, link.SiloId))
305320

306321
// Save plan into Terraform state
307322
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)

0 commit comments

Comments
 (0)