Skip to content

feat(ipam): filter by id & add address with cidr #2362

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 17 additions & 8 deletions docs/data-sources/ipam_ip.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ Gets information about IP managed by IPAM service. IPAM service is used for dhcp
### Instance Private Network IP

```hcl
# Get info by ipam ip id
data "scaleway_ipam_ip" "by_id" {
ipam_ip_id = "11111111-1111-1111-1111-111111111111"
}

# Get Instance IP in a private network
resource "scaleway_instance_private_nic" "nic" {
server_id = scaleway_instance_server.server.id
Expand Down Expand Up @@ -61,25 +66,28 @@ data "scaleway_ipam_ip" "by_name" {

## Argument Reference

- `type` - (Required) The type of IP to search for (ipv4, ipv6).
- `ipam_ip_id` - (Optional) The IPAM IP ID. Cannot be used with the rest of the arguments.

- `type` - (Optional) The type of IP to search for (ipv4, ipv6). Cannot be used with `ipam_ip_id`.

- `private_network_id` - (Optional) The ID of the private network the IP belong to.
- `private_network_id` - (Optional) The ID of the private network the IP belong to. Cannot be used with `ipam_ip_id`.

- `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.
- `resource` - (Optional) Filter by resource ID, type or name. Cannot be used with `ipam_ip_id`.
If specified, `type` is required, and at least one of `id` or `name` must be set.
- `id` - The ID of the resource that the IP is bound to.
- `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.
- `name` - The name of the resource to get the IP from.

- `mac_address` - (Optional) The Mac Address linked to the IP.
- `mac_address` - (Optional) The Mac Address linked to the IP. Cannot be used with `ipam_ip_id`.

- `region` - (Defaults to [provider](../index.md#zone) `region`) The [region](../guides/regions_and_zones.md#regions) in which the IP exists.

- `tags` (Optional) The tags associated with the IP.
- `tags` (Optional) The tags associated with the IP. Cannot be used with `ipam_ip_id`.
As datasource only returns one IP, the search with given tags must return only one result.

- `zonal` - (Optional) Only IPs that are zonal, and in this zone, will be returned.

- `attached` - (Optional) Defines whether to filter only for IPs which are attached to a resource.
- `attached` - (Optional) Defines whether to filter only for IPs which are attached to a resource. Cannot be used with `ipam_ip_id`.

- `project_id` - (Defaults to [provider](../index.md#project_id) `project_id`) The ID of the project the IP is associated with.

Expand All @@ -89,5 +97,6 @@ data "scaleway_ipam_ip" "by_name" {

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

- `id` - The ID of the IP in IPAM
- `address` - The IP address
- `id` - The ID of the IP in IPAM.
- `address` - The IP address.
- `address_cidr` - the IP address with a CIDR notation.
194 changes: 121 additions & 73 deletions scaleway/data_source_ipam_ip.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,24 @@ func dataSourceScalewayIPAMIP() *schema.Resource {
ReadContext: dataSourceScalewayIPAMIPRead,
Schema: map[string]*schema.Schema{
// Input
"ipam_ip_id": {
Type: schema.TypeString,
Optional: true,
Description: "The ID of the IPAM IP",
ValidateFunc: validationUUIDorUUIDWithLocality(),
ConflictsWith: []string{"private_network_id", "resource", "mac_address", "type", "tags", "attached"},
},
"private_network_id": {
Type: schema.TypeString,
Optional: true,
Description: "The private Network to filter for",
Type: schema.TypeString,
Optional: true,
Description: "The private Network to filter for",
ConflictsWith: []string{"ipam_ip_id"},
},
"resource": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
ConflictsWith: []string{"ipam_ip_id"},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"id": {
Expand All @@ -46,14 +55,16 @@ func dataSourceScalewayIPAMIP() *schema.Resource {
},
},
"mac_address": {
Type: schema.TypeString,
Optional: true,
Description: "The MAC address to filter for",
Type: schema.TypeString,
Optional: true,
Description: "The MAC address to filter for",
ConflictsWith: []string{"ipam_ip_id"},
},
"type": {
Type: schema.TypeString,
Required: true,
Description: "IP Type (ipv4, ipv6)",
Type: schema.TypeString,
Optional: true,
Description: "IP Type (ipv4, ipv6)",
ConflictsWith: []string{"ipam_ip_id"},
ValidateDiagFunc: func(i interface{}, path cty.Path) diag.Diagnostics {
switch i.(string) {
case "ipv4":
Expand All @@ -75,22 +86,30 @@ func dataSourceScalewayIPAMIP() *schema.Resource {
Elem: &schema.Schema{
Type: schema.TypeString,
},
Optional: true,
Description: "The tags associated with the IP",
Optional: true,
Description: "The tags associated with the IP",
ConflictsWith: []string{"ipam_ip_id"},
},
"attached": {
Type: schema.TypeBool,
Optional: true,
Description: "Defines whether to filter only for IPs which are attached to a resource",
Type: schema.TypeBool,
Optional: true,
Description: "Defines whether to filter only for IPs which are attached to a resource",
ConflictsWith: []string{"ipam_ip_id"},
},
"zonal": zoneSchema(),
"region": regionSchema(),
"project_id": projectIDSchema(),
"organization_id": organizationIDSchema(),
// Computed
"address": {
Type: schema.TypeString,
Computed: true,
Type: schema.TypeString,
Computed: true,
Description: "The IP address",
},
"address_cidr": {
Type: schema.TypeString,
Computed: true,
Description: "The IP address with a CIDR notation",
},
},
}
Expand All @@ -102,71 +121,100 @@ func dataSourceScalewayIPAMIPRead(ctx context.Context, d *schema.ResourceData, m
return diag.FromErr(err)
}

resources, resourcesOk := d.GetOk("resource")
if resourcesOk {
resourceList := resources.([]interface{})
if len(resourceList) > 0 {
resourceMap := resourceList[0].(map[string]interface{})
id, idExists := resourceMap["id"].(string)
name, nameExists := resourceMap["name"].(string)
var address, addressCidr string
IPID, ok := d.GetOk("ipam_ip_id")
if !ok {
resources, resourcesOk := d.GetOk("resource")
if resourcesOk {
resourceList := resources.([]interface{})
if len(resourceList) > 0 {
resourceMap := resourceList[0].(map[string]interface{})
id, idExists := resourceMap["id"].(string)
name, nameExists := resourceMap["name"].(string)

if (idExists && id == "") && (nameExists && name == "") {
if (idExists && id == "") && (nameExists && name == "") {
return diag.Diagnostics{{
Severity: diag.Error,
Summary: "Missing field",
Detail: "Either 'id' or 'name' must be provided in 'resource'",
}}
}
}
}

req := &ipam.ListIPsRequest{
Region: region,
ProjectID: expandStringPtr(d.Get("project_id")),
Zonal: expandStringPtr(d.Get("zonal")),
ResourceID: expandStringPtr(expandLastID(d.Get("resource.0.id"))),
ResourceType: ipam.ResourceType(d.Get("resource.0.type").(string)),
ResourceName: expandStringPtr(d.Get("resource.0.name").(string)),
MacAddress: expandStringPtr(d.Get("mac_address")),
Tags: expandStrings(d.Get("tags")),
OrganizationID: expandStringPtr(d.Get("organization_id")),
PrivateNetworkID: expandStringPtr(d.Get("private_network_id")),
}

ipType, ipTypeExist := d.GetOk("type")
if ipTypeExist {
switch ipType.(string) {
case "ipv4":
req.IsIPv6 = scw.BoolPtr(false)
case "ipv6":
req.IsIPv6 = scw.BoolPtr(true)
default:
return diag.Diagnostics{{
Severity: diag.Error,
Summary: "Missing field",
Detail: "Either 'id' or 'name' must be provided in 'resource'",
Severity: diag.Error,
Summary: "Invalid IP Type",
Detail: "Expected ipv4 or ipv6",
AttributePath: cty.GetAttrPath("type"),
}}
}
}
}

req := &ipam.ListIPsRequest{
Region: region,
ProjectID: expandStringPtr(d.Get("project_id")),
Zonal: expandStringPtr(d.Get("zonal")),
ResourceID: expandStringPtr(expandLastID(d.Get("resource.0.id"))),
ResourceType: ipam.ResourceType(d.Get("resource.0.type").(string)),
ResourceName: expandStringPtr(d.Get("resource.0.name").(string)),
MacAddress: expandStringPtr(d.Get("mac_address")),
Tags: expandStrings(d.Get("tags")),
OrganizationID: expandStringPtr(d.Get("organization_id")),
PrivateNetworkID: expandStringPtr(d.Get("private_network_id")),
}
attached, attachedExists := d.GetOk("attached")
if attachedExists {
req.Attached = expandBoolPtr(attached)
}

switch d.Get("type").(string) {
case "ipv4":
req.IsIPv6 = scw.BoolPtr(false)
case "ipv6":
req.IsIPv6 = scw.BoolPtr(true)
default:
return diag.Diagnostics{{
Severity: diag.Error,
Summary: "Invalid IP Type",
Detail: "Expected ipv4 or ipv6",
AttributePath: cty.GetAttrPath("type"),
}}
}
resp, err := api.ListIPs(req, scw.WithAllPages(), scw.WithContext(ctx))
if err != nil {
return diag.FromErr(err)
}
if len(resp.IPs) == 0 {
return diag.FromErr(fmt.Errorf("no ip found with given filters"))
}
if len(resp.IPs) > 1 {
return diag.FromErr(fmt.Errorf("more than one ip found with given filter"))
}

attached, attachedExists := d.GetOk("attached")
if attachedExists {
req.Attached = expandBoolPtr(attached)
}
ip := resp.IPs[0]
IPID = ip.ID

resp, err := api.ListIPs(req, scw.WithAllPages(), scw.WithContext(ctx))
if err != nil {
return diag.FromErr(err)
}
if len(resp.IPs) == 0 {
return diag.FromErr(fmt.Errorf("no ip found with given filters"))
}
if len(resp.IPs) > 1 {
return diag.FromErr(fmt.Errorf("more than one ip found with given filter"))
}
address = ip.Address.IP.String()
addressCidr, err = flattenIPNet(ip.Address)
if err != nil {
return diag.FromErr(err)
}
} else {
res, err := api.GetIP(&ipam.GetIPRequest{
Region: region,
IPID: expandID(IPID.(string)),
}, scw.WithContext(ctx))
if err != nil {
return diag.FromErr(err)
}

ip := resp.IPs[0]
address = res.Address.IP.String()
addressCidr, err = flattenIPNet(res.Address)
if err != nil {
return diag.FromErr(err)
}
}

d.SetId(ip.ID)
_ = d.Set("address", ip.Address.IP.String())
d.SetId(IPID.(string))
_ = d.Set("address", address)
_ = d.Set("address_cidr", addressCidr)

return nil
}
42 changes: 42 additions & 0 deletions scaleway/data_source_ipam_ip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,45 @@ func TestAccScalewayDataSourceIPAMIP_RDB(t *testing.T) {
},
})
}

func TestAccScalewayDataSourceIPAMIP_ID(t *testing.T) {
tt := NewTestTools(t)
defer tt.Cleanup()
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: tt.ProviderFactories,
CheckDestroy: testAccCheckScalewayIPAMIPDestroy(tt),
Steps: []resource.TestStep{
{
Config: `
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.5/22"
source {
private_network_id = scaleway_vpc_private_network.pn01.id
}
}

data "scaleway_ipam_ip" "by_id" {
ipam_ip_id = scaleway_ipam_ip.ip01.id
}
`,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.scaleway_ipam_ip.by_id", "address", "172.16.32.5"),
resource.TestCheckResourceAttr("data.scaleway_ipam_ip.by_id", "address_cidr", "172.16.32.5/22"),
resource.TestCheckResourceAttrPair("data.scaleway_ipam_ip.by_id", "ipam_ip_id", "scaleway_ipam_ip.ip01", "id"),
),
},
},
})
}
Loading