Skip to content

Commit

Permalink
feat(ipam): add ipam ip resource
Browse files Browse the repository at this point in the history
  • Loading branch information
yfodil committed Oct 27, 2023
1 parent 17beb4e commit 485e443
Show file tree
Hide file tree
Showing 14 changed files with 3,335 additions and 7 deletions.
1 change: 1 addition & 0 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ jobs:
- Iam
- Instance
- Iot
- IPAM
- K8S
- Lb
- Marketplace
Expand Down
113 changes: 113 additions & 0 deletions docs/resources/ipam_ip.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
---
subcategory: "IPAM"
page_title: "Scaleway: scaleway_ipam_ip"
---

# scaleway_ipam_ip

Books and manages Scaleway IPAM IPs.

## Example

### Basic

```hcl
resource "scaleway_vpc" "vpc01" {
name = "my vpc"
}
resource "scaleway_vpc_private_network" "pn01" {
vpc_id = scaleway_vpc.vpc01.id
ipv4_subnet {
subnet = "172.16.32.0/22"
}
}
resource "scaleway_ipam_ip" "ip01" {
source {
private_network_id = scaleway_vpc_private_network.pn01.id
}
}
```

### Request a specific IPv4

```hcl
resource "scaleway_vpc" "vpc01" {
name = "my vpc"
}
resource "scaleway_vpc_private_network" "pn01" {
vpc_id = scaleway_vpc.vpc01.id
ipv4_subnet {
subnet = "172.16.32.0/22"
}
}
resource "scaleway_ipam_ip" "ip01" {
address = "172.16.32.7/22"
source {
private_network_id = scaleway_vpc_private_network.pn01.id
}
}
```

### Request an IPv6

```hcl
resource "scaleway_vpc" "vpc01" {
name = "my vpc"
}
resource "scaleway_vpc_private_network" "pn01" {
vpc_id = scaleway_vpc.vpc01.id
ipv6_subnets {
subnet = "fd46:78ab:30b8:177c::/64"
}
}
resource "scaleway_ipam_ip" "ip01" {
is_ipv6 = true
source {
private_network_id = scaleway_vpc_private_network.pn01.id
}
}
```

## Arguments Reference

The following arguments are supported:

- `address` - (Optional) Request a specific IP in the requested source pool.
- `tags` - (Optional) The tags associated with the IP.
- `source` - (Required) The source in which to book the IP.
- `zonal` - The zone the IP lives in if the IP is a public zoned one
- `private_network_id` - The private network the IP lives in if the IP is a private IP.
- `subnet_id` - The private network subnet the IP lives in if the IP is a private IP in a private network.
- `is_ipv6` - (Optional) Defines whether to request an IPv6 instead of an IPv4.
- `region` - (Defaults to [provider](../index.md#region) `region`) The [region](../guides/regions_and_zones.md#regions) of the IP.
- `project_id` - (Defaults to [provider](../index.md#project_id) `project_id`) The ID of the project the IP is associated with.

## Attributes Reference

In addition to all above arguments, the following attributes are exported:

- `id` - The ID of the IP in IPAM.
- `resource` - The IP resource.
- `id` - The ID of the resource that the IP is bound to.
- `type` - The type of resource the IP is attached to.
- `name` - The name of the resource the IP is attached to.
- `mac_address` - The MAC Address of the resource the IP is attached to.
- `created_at` - Date and time of IP's creation (RFC 3339 format).
- `updated_at` - Date and time of IP's last update (RFC 3339 format).
- `zone` - The zone of the IP.

~> **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

## Import

IPAM IPs can be imported using the `{region}/{id}`, e.g.

```bash
$ terraform import scaleway_ipam_ip.ip_demo fr-par/11111111-1111-1111-1111-111111111111
```
24 changes: 24 additions & 0 deletions scaleway/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -1137,3 +1137,27 @@ func testAccCheckScalewayResourceIDChanged(resourceName string, resourceID *stri
return nil
}
}

// testAccCheckScalewayResourceRawIDMatches asserts the equality of IDs from two specified attributes of two Scaleway resources.
func testAccCheckScalewayResourceRawIDMatches(res1, attr1, res2, attr2 string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs1, ok1 := s.RootModule().Resources[res1]
if !ok1 {
return fmt.Errorf("not found: %s", res1)
}

rs2, ok2 := s.RootModule().Resources[res2]
if !ok2 {
return fmt.Errorf("not found: %s", res2)
}

id1 := expandID(rs1.Primary.Attributes[attr1])
id2 := expandID(rs2.Primary.Attributes[attr2])

if id1 != id2 {
return fmt.Errorf("ID mismatch: %s from resource %s does not match ID %s from resource %s", id1, res1, id2, res2)
}

return nil
}
}
10 changes: 7 additions & 3 deletions scaleway/helpers_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
"github.com/scaleway/scaleway-sdk-go/api/vpc/v1"
"github.com/scaleway/scaleway-sdk-go/api/vpc/v2"
"github.com/scaleway/scaleway-sdk-go/scw"
)

Expand Down Expand Up @@ -292,15 +292,19 @@ func preparePrivateNIC(
zonedID, pnExist := r["pn_id"]
privateNetworkID := expandID(zonedID.(string))
if pnExist {
region, err := server.Zone.Region()
if err != nil {
return nil, err
}
currentPN, err := vpcAPI.GetPrivateNetwork(&vpc.GetPrivateNetworkRequest{
PrivateNetworkID: expandID(privateNetworkID),
Zone: server.Zone,
Region: region,
}, scw.WithContext(ctx))
if err != nil {
return nil, err
}
query := &instance.CreatePrivateNICRequest{
Zone: currentPN.Zone,
Zone: server.Zone,
ServerID: server.ID,
PrivateNetworkID: currentPN.ID,
}
Expand Down
88 changes: 88 additions & 0 deletions scaleway/helpers_ipam.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package scaleway

import (
"net"
"strings"

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

// ipamAPIWithRegionAndID returns a new ipam API with locality and ID extracted from the state
func ipamAPIWithRegionAndID(m interface{}, id string) (*ipam.API, scw.Region, string, error) {
meta := m.(*Meta)
ipamAPI := ipam.NewAPI(meta.scwClient)

region, ID, err := parseRegionalID(id)
if err != nil {
return nil, "", "", err
}
return ipamAPI, region, ID, err
}

// expandLastID expand the last ID in a potential composed ID
// region/id1/id2 -> id2
// region/id1 -> id1
Expand All @@ -39,3 +52,78 @@ func expandLastID(i interface{}) string {

return composedID
}

func expandIPSource(raw interface{}) *ipam.Source {
if raw == nil || len(raw.([]interface{})) != 1 {
return nil
}

rawMap := raw.([]interface{})[0].(map[string]interface{})
return &ipam.Source{
Zonal: expandStringPtr(rawMap["zonal"].(string)),
PrivateNetworkID: expandStringPtr(expandID(rawMap["private_network_id"].(string))),
SubnetID: expandStringPtr(rawMap["subnet_id"].(string)),
}
}

func flattenIPSource(source *ipam.Source, privateNetworkID string) interface{} {
if source == nil {
return nil
}
return []map[string]interface{}{
{
"zonal": flattenStringPtr(source.Zonal),
"private_network_id": privateNetworkID,
"subnet_id": flattenStringPtr(source.SubnetID),
},
}
}

func flattenIPResource(resource *ipam.Resource) interface{} {
if resource == nil {
return nil
}
return []map[string]interface{}{
{
"type": resource.Type.String(),
"id": resource.ID,
"mac_address": flattenStringPtr(resource.MacAddress),
"name": flattenStringPtr(resource.Name),
},
}
}

func checkSubnetIDInFlattenedSubnets(subnetID string, flattenedSubnets interface{}) bool {
for _, subnet := range flattenedSubnets.([]map[string]interface{}) {
if subnet["id"].(string) == subnetID {
return true
}
}
return false
}

func diffSuppressFuncStandaloneIPandCIDR(k, old, new string, _ *schema.ResourceData) bool {

Check warning on line 105 in scaleway/helpers_ipam.go

View workflow job for this annotation

GitHub Actions / lint

unused-parameter: parameter 'k' seems to be unused, consider removing or renaming it as _ (revive)
oldIP, oldNet, errOld := net.ParseCIDR(old)
if errOld != nil {
oldIP = net.ParseIP(old)
}

newIP, newNet, errNew := net.ParseCIDR(new)
if errNew != nil {
newIP = net.ParseIP(new)
}

if oldIP != nil && newIP != nil && oldIP.Equal(newIP) {
return true
}

if oldNet != nil && newIP != nil && oldNet.Contains(newIP) {
return true
}

if newNet != nil && oldIP != nil && newNet.Contains(oldIP) {
return true
}

return false
}
4 changes: 2 additions & 2 deletions scaleway/helpers_vpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,13 @@ func vpcAPIWithRegionAndID(m interface{}, id string) (*v2.API, scw.Region, strin
return vpcAPI, region, ID, err
}

func vpcAPI(m interface{}) (*v1.API, error) {
func vpcAPI(m interface{}) (*v2.API, error) {
meta, ok := m.(*Meta)
if !ok {
return nil, fmt.Errorf("wrong type: %T", m)
}

return v1.NewAPI(meta.scwClient), nil
return v2.NewAPI(meta.scwClient), nil
}

func expandSubnets(d *schema.ResourceData) (ipv4Subnets []scw.IPNet, ipv6Subnets []scw.IPNet, err error) {
Expand Down
1 change: 1 addition & 0 deletions scaleway/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ func Provider(config *ProviderConfig) plugin.ProviderFunc {
"scaleway_iot_device": resourceScalewayIotDevice(),
"scaleway_iot_route": resourceScalewayIotRoute(),
"scaleway_iot_network": resourceScalewayIotNetwork(),
"scaleway_ipam_ip": resourceScalewayIPAMIP(),
"scaleway_k8s_cluster": resourceScalewayK8SCluster(),
"scaleway_k8s_pool": resourceScalewayK8SPool(),
"scaleway_lb": resourceScalewayLb(),
Expand Down
Loading

0 comments on commit 485e443

Please sign in to comment.