Skip to content

Commit 4464578

Browse files
authored
feat(ipam): add ipam ip resource (#2188)
* feat(ipam): add ipam ip resource * lint & update cassettes * update cassette * remove vpc v1 * fix documentation link
1 parent 6f03082 commit 4464578

23 files changed

+8328
-4825
lines changed

.github/workflows/nightly.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ jobs:
2323
- Iam
2424
- Instance
2525
- Iot
26+
- IPAM
2627
- K8S
2728
- Lb
2829
- Marketplace

docs/data-sources/ipam_ip.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ data "scaleway_ipam_ip" "by_name" {
6767

6868
- `resource` - (Optional) Filter by resource ID, type or name. If specified, `type` is required, and at least one of `id` or `name` must be set.
6969
- `id` - The ID of the resource that the IP is bound to.
70-
- `type` - The type of the resource to get the IP from. [Documentation](https://pkg.go.dev/github.com/scaleway/scaleway-sdk-go@v1.0.0-beta.21.0.20231020161050-699490ebeefd/api/ipam/v1#pkg-constants) with type list.
70+
- `type` - The type of the resource to get the IP from. [Documentation](https://pkg.go.dev/github.com/scaleway/scaleway-sdk-go@master/api/ipam/v1#pkg-constants) with type list.
7171
- `name` - The name of the resource to get the IP from.
7272

7373
- `mac_address` - (Optional) The Mac Address linked to the IP.

docs/resources/ipam_ip.md

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
---
2+
subcategory: "IPAM"
3+
page_title: "Scaleway: scaleway_ipam_ip"
4+
---
5+
6+
# scaleway_ipam_ip
7+
8+
Books and manages Scaleway IPAM IPs.
9+
10+
## Example
11+
12+
### Basic
13+
14+
```hcl
15+
resource "scaleway_vpc" "vpc01" {
16+
name = "my vpc"
17+
}
18+
19+
resource "scaleway_vpc_private_network" "pn01" {
20+
vpc_id = scaleway_vpc.vpc01.id
21+
ipv4_subnet {
22+
subnet = "172.16.32.0/22"
23+
}
24+
}
25+
26+
resource "scaleway_ipam_ip" "ip01" {
27+
source {
28+
private_network_id = scaleway_vpc_private_network.pn01.id
29+
}
30+
}
31+
```
32+
33+
### Request a specific IPv4
34+
35+
```hcl
36+
resource "scaleway_vpc" "vpc01" {
37+
name = "my vpc"
38+
}
39+
40+
resource "scaleway_vpc_private_network" "pn01" {
41+
vpc_id = scaleway_vpc.vpc01.id
42+
ipv4_subnet {
43+
subnet = "172.16.32.0/22"
44+
}
45+
}
46+
47+
resource "scaleway_ipam_ip" "ip01" {
48+
address = "172.16.32.7/22"
49+
source {
50+
private_network_id = scaleway_vpc_private_network.pn01.id
51+
}
52+
}
53+
```
54+
55+
### Request an IPv6
56+
57+
```hcl
58+
resource "scaleway_vpc" "vpc01" {
59+
name = "my vpc"
60+
}
61+
62+
resource "scaleway_vpc_private_network" "pn01" {
63+
vpc_id = scaleway_vpc.vpc01.id
64+
ipv6_subnets {
65+
subnet = "fd46:78ab:30b8:177c::/64"
66+
}
67+
}
68+
69+
resource "scaleway_ipam_ip" "ip01" {
70+
is_ipv6 = true
71+
source {
72+
private_network_id = scaleway_vpc_private_network.pn01.id
73+
}
74+
}
75+
```
76+
77+
## Arguments Reference
78+
79+
The following arguments are supported:
80+
81+
- `address` - (Optional) Request a specific IP in the requested source pool.
82+
- `tags` - (Optional) The tags associated with the IP.
83+
- `source` - (Required) The source in which to book the IP.
84+
- `zonal` - The zone the IP lives in if the IP is a public zoned one
85+
- `private_network_id` - The private network the IP lives in if the IP is a private IP.
86+
- `subnet_id` - The private network subnet the IP lives in if the IP is a private IP in a private network.
87+
- `is_ipv6` - (Optional) Defines whether to request an IPv6 instead of an IPv4.
88+
- `region` - (Defaults to [provider](../index.md#region) `region`) The [region](../guides/regions_and_zones.md#regions) of the IP.
89+
- `project_id` - (Defaults to [provider](../index.md#project_id) `project_id`) The ID of the project the IP is associated with.
90+
91+
## Attributes Reference
92+
93+
In addition to all above arguments, the following attributes are exported:
94+
95+
- `id` - The ID of the IP in IPAM.
96+
- `resource` - The IP resource.
97+
- `id` - The ID of the resource that the IP is bound to.
98+
- `type` - The type of resource the IP is attached to.
99+
- `name` - The name of the resource the IP is attached to.
100+
- `mac_address` - The MAC Address of the resource the IP is attached to.
101+
- `created_at` - Date and time of IP's creation (RFC 3339 format).
102+
- `updated_at` - Date and time of IP's last update (RFC 3339 format).
103+
- `zone` - The zone of the IP.
104+
105+
~> **Important:** IPAM IPs' IDs are [regional](../guides/regions_and_zones.md#resource-ids), which means they are of the form `{region}/{id}`, e.g. `fr-par/11111111-1111-1111-1111-111111111111
106+
107+
## Import
108+
109+
IPAM IPs can be imported using the `{region}/{id}`, e.g.
110+
111+
```bash
112+
$ terraform import scaleway_ipam_ip.ip_demo fr-par/11111111-1111-1111-1111-111111111111
113+
```

scaleway/helpers.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,3 +1137,27 @@ func testAccCheckScalewayResourceIDChanged(resourceName string, resourceID *stri
11371137
return nil
11381138
}
11391139
}
1140+
1141+
// testAccCheckScalewayResourceRawIDMatches asserts the equality of IDs from two specified attributes of two Scaleway resources.
1142+
func testAccCheckScalewayResourceRawIDMatches(res1, attr1, res2, attr2 string) resource.TestCheckFunc {
1143+
return func(s *terraform.State) error {
1144+
rs1, ok1 := s.RootModule().Resources[res1]
1145+
if !ok1 {
1146+
return fmt.Errorf("not found: %s", res1)
1147+
}
1148+
1149+
rs2, ok2 := s.RootModule().Resources[res2]
1150+
if !ok2 {
1151+
return fmt.Errorf("not found: %s", res2)
1152+
}
1153+
1154+
id1 := expandID(rs1.Primary.Attributes[attr1])
1155+
id2 := expandID(rs2.Primary.Attributes[attr2])
1156+
1157+
if id1 != id2 {
1158+
return fmt.Errorf("ID mismatch: %s from resource %s does not match ID %s from resource %s", id1, res1, id2, res2)
1159+
}
1160+
1161+
return nil
1162+
}
1163+
}

scaleway/helpers_instance.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
"github.com/hashicorp/terraform-plugin-log/tflog"
1414
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1515
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
16-
"github.com/scaleway/scaleway-sdk-go/api/vpc/v1"
16+
"github.com/scaleway/scaleway-sdk-go/api/vpc/v2"
1717
"github.com/scaleway/scaleway-sdk-go/scw"
1818
)
1919

@@ -292,15 +292,19 @@ func preparePrivateNIC(
292292
zonedID, pnExist := r["pn_id"]
293293
privateNetworkID := expandID(zonedID.(string))
294294
if pnExist {
295+
region, err := server.Zone.Region()
296+
if err != nil {
297+
return nil, err
298+
}
295299
currentPN, err := vpcAPI.GetPrivateNetwork(&vpc.GetPrivateNetworkRequest{
296300
PrivateNetworkID: expandID(privateNetworkID),
297-
Zone: server.Zone,
301+
Region: region,
298302
}, scw.WithContext(ctx))
299303
if err != nil {
300304
return nil, err
301305
}
302306
query := &instance.CreatePrivateNICRequest{
303-
Zone: currentPN.Zone,
307+
Zone: server.Zone,
304308
ServerID: server.ID,
305309
PrivateNetworkID: currentPN.ID,
306310
}

scaleway/helpers_ipam.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package scaleway
22

33
import (
4+
"net"
45
"strings"
56

67
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -22,6 +23,18 @@ func ipamAPIWithRegion(d *schema.ResourceData, m interface{}) (*ipam.API, scw.Re
2223
return ipamAPI, region, nil
2324
}
2425

26+
// ipamAPIWithRegionAndID returns a new ipam API with locality and ID extracted from the state
27+
func ipamAPIWithRegionAndID(m interface{}, id string) (*ipam.API, scw.Region, string, error) {
28+
meta := m.(*Meta)
29+
ipamAPI := ipam.NewAPI(meta.scwClient)
30+
31+
region, ID, err := parseRegionalID(id)
32+
if err != nil {
33+
return nil, "", "", err
34+
}
35+
return ipamAPI, region, ID, err
36+
}
37+
2538
// expandLastID expand the last ID in a potential composed ID
2639
// region/id1/id2 -> id2
2740
// region/id1 -> id1
@@ -39,3 +52,78 @@ func expandLastID(i interface{}) string {
3952

4053
return composedID
4154
}
55+
56+
func expandIPSource(raw interface{}) *ipam.Source {
57+
if raw == nil || len(raw.([]interface{})) != 1 {
58+
return nil
59+
}
60+
61+
rawMap := raw.([]interface{})[0].(map[string]interface{})
62+
return &ipam.Source{
63+
Zonal: expandStringPtr(rawMap["zonal"].(string)),
64+
PrivateNetworkID: expandStringPtr(expandID(rawMap["private_network_id"].(string))),
65+
SubnetID: expandStringPtr(rawMap["subnet_id"].(string)),
66+
}
67+
}
68+
69+
func flattenIPSource(source *ipam.Source, privateNetworkID string) interface{} {
70+
if source == nil {
71+
return nil
72+
}
73+
return []map[string]interface{}{
74+
{
75+
"zonal": flattenStringPtr(source.Zonal),
76+
"private_network_id": privateNetworkID,
77+
"subnet_id": flattenStringPtr(source.SubnetID),
78+
},
79+
}
80+
}
81+
82+
func flattenIPResource(resource *ipam.Resource) interface{} {
83+
if resource == nil {
84+
return nil
85+
}
86+
return []map[string]interface{}{
87+
{
88+
"type": resource.Type.String(),
89+
"id": resource.ID,
90+
"mac_address": flattenStringPtr(resource.MacAddress),
91+
"name": flattenStringPtr(resource.Name),
92+
},
93+
}
94+
}
95+
96+
func checkSubnetIDInFlattenedSubnets(subnetID string, flattenedSubnets interface{}) bool {
97+
for _, subnet := range flattenedSubnets.([]map[string]interface{}) {
98+
if subnet["id"].(string) == subnetID {
99+
return true
100+
}
101+
}
102+
return false
103+
}
104+
105+
func diffSuppressFuncStandaloneIPandCIDR(_, oldValue, newValue string, _ *schema.ResourceData) bool {
106+
oldIP, oldNet, errOld := net.ParseCIDR(oldValue)
107+
if errOld != nil {
108+
oldIP = net.ParseIP(oldValue)
109+
}
110+
111+
newIP, newNet, errNew := net.ParseCIDR(newValue)
112+
if errNew != nil {
113+
newIP = net.ParseIP(newValue)
114+
}
115+
116+
if oldIP != nil && newIP != nil && oldIP.Equal(newIP) {
117+
return true
118+
}
119+
120+
if oldNet != nil && newIP != nil && oldNet.Contains(newIP) {
121+
return true
122+
}
123+
124+
if newNet != nil && oldIP != nil && newNet.Contains(oldIP) {
125+
return true
126+
}
127+
128+
return false
129+
}

scaleway/helpers_vpc.go

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,15 @@ import (
99

1010
"github.com/hashicorp/go-cty/cty"
1111
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
12-
v1 "github.com/scaleway/scaleway-sdk-go/api/vpc/v1"
13-
v2 "github.com/scaleway/scaleway-sdk-go/api/vpc/v2"
12+
"github.com/scaleway/scaleway-sdk-go/api/vpc/v2"
1413
"github.com/scaleway/scaleway-sdk-go/scw"
1514
validator "github.com/scaleway/scaleway-sdk-go/validation"
1615
)
1716

18-
// vpcAPIWithZoneAndID
19-
func vpcAPIWithZoneAndID(m interface{}, id string) (*v1.API, scw.Zone, string, error) {
20-
meta := m.(*Meta)
21-
vpcAPI := v1.NewAPI(meta.scwClient)
22-
23-
zone, ID, err := parseZonedID(id)
24-
if err != nil {
25-
return nil, "", "", err
26-
}
27-
return vpcAPI, zone, ID, err
28-
}
29-
3017
// vpcAPIWithRegion returns a new VPC API and the region for a Create request
31-
func vpcAPIWithRegion(d *schema.ResourceData, m interface{}) (*v2.API, scw.Region, error) {
18+
func vpcAPIWithRegion(d *schema.ResourceData, m interface{}) (*vpc.API, scw.Region, error) {
3219
meta := m.(*Meta)
33-
vpcAPI := v2.NewAPI(meta.scwClient)
20+
vpcAPI := vpc.NewAPI(meta.scwClient)
3421

3522
region, err := extractRegion(d, meta)
3623
if err != nil {
@@ -40,9 +27,9 @@ func vpcAPIWithRegion(d *schema.ResourceData, m interface{}) (*v2.API, scw.Regio
4027
}
4128

4229
// vpcAPIWithRegionAndID returns a new VPC API with locality and ID extracted from the state
43-
func vpcAPIWithRegionAndID(m interface{}, id string) (*v2.API, scw.Region, string, error) {
30+
func vpcAPIWithRegionAndID(m interface{}, id string) (*vpc.API, scw.Region, string, error) {
4431
meta := m.(*Meta)
45-
vpcAPI := v2.NewAPI(meta.scwClient)
32+
vpcAPI := vpc.NewAPI(meta.scwClient)
4633

4734
region, ID, err := parseRegionalID(id)
4835
if err != nil {
@@ -51,13 +38,13 @@ func vpcAPIWithRegionAndID(m interface{}, id string) (*v2.API, scw.Region, strin
5138
return vpcAPI, region, ID, err
5239
}
5340

54-
func vpcAPI(m interface{}) (*v1.API, error) {
41+
func vpcAPI(m interface{}) (*vpc.API, error) {
5542
meta, ok := m.(*Meta)
5643
if !ok {
5744
return nil, fmt.Errorf("wrong type: %T", m)
5845
}
5946

60-
return v1.NewAPI(meta.scwClient), nil
47+
return vpc.NewAPI(meta.scwClient), nil
6148
}
6249

6350
func expandSubnets(d *schema.ResourceData) (ipv4Subnets []scw.IPNet, ipv6Subnets []scw.IPNet, err error) {
@@ -89,7 +76,7 @@ func flattenAndSortSubnets(sub interface{}) (interface{}, interface{}) {
8976
switch subnets := sub.(type) {
9077
case []scw.IPNet:
9178
return flattenAndSortIPNetSubnets(subnets)
92-
case []*v2.Subnet:
79+
case []*vpc.Subnet:
9380
return flattenAndSortSubnetV2s(subnets)
9481
default:
9582
return "", nil
@@ -134,7 +121,7 @@ func flattenAndSortIPNetSubnets(subnets []scw.IPNet) (interface{}, interface{})
134121
return flattenedipv4Subnets, flattenedipv6Subnets
135122
}
136123

137-
func flattenAndSortSubnetV2s(subnets []*v2.Subnet) (interface{}, interface{}) {
124+
func flattenAndSortSubnetV2s(subnets []*vpc.Subnet) (interface{}, interface{}) {
138125
if subnets == nil {
139126
return "", nil
140127
}

scaleway/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ func Provider(config *ProviderConfig) plugin.ProviderFunc {
133133
"scaleway_iot_device": resourceScalewayIotDevice(),
134134
"scaleway_iot_route": resourceScalewayIotRoute(),
135135
"scaleway_iot_network": resourceScalewayIotNetwork(),
136+
"scaleway_ipam_ip": resourceScalewayIPAMIP(),
136137
"scaleway_k8s_cluster": resourceScalewayK8SCluster(),
137138
"scaleway_k8s_pool": resourceScalewayK8SPool(),
138139
"scaleway_lb": resourceScalewayLb(),

0 commit comments

Comments
 (0)