Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,44 @@ The following attributes are exported:
* `last_seen` - The last time this IP address answered ping probes.
* `edit_date` - The last time this resource was modified.

#### The `phpipam_first_free_address` Resource - Dynamic IPs creation

The `phpipam_first_free_address` resource allow to create automatically
new IP in defined network without execution Terraform data instruction. You can use it
to create several IP addresses automatically. This resource support the same arguments as
phpipam_address. An example usage is below.

⚠️ **NOTE:** This is experimental new feature. You can use Terraform count
instruction. But be carefull, phpIPAM currently has a bug https://github.com/phpipam/phpipam/issues/2960
Use resource with count option only with limitted terraform threads count: `terraform apply -parallelism=1`
.

**Example:**

```
// Look up the subnet
data "phpipam_subnet" "subnet" {
subnet_address = "10.10.2.0"
subnet_mask = 24
}

// Reserve the address. Note that we use ignore_changes here to ensure that we
// don't end up re-allocating this address on future Terraform runs.
resource "phpipam_first_free_address" {
count = 3

subnet_id = data.phpipam_subnet.subnet.subnet_id
hostname = "tf-test-host.example.internal"
description = "Managed by Terraform"

lifecycle {
ignore_changes = [
subnet_id,
]
}
}
```

#### The `phpipam_section` Resource

The `phpipam_section` resource manages a PHPIPAM section - a top-level category
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKe
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.0.0-20160517064435-50d4dbd4eb0e/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
Expand Down
9 changes: 5 additions & 4 deletions plugin/providers/phpipam/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@ func Provider() terraform.ResourceProvider {
},

ResourcesMap: map[string]*schema.Resource{
"phpipam_address": resourcePHPIPAMAddress(),
"phpipam_section": resourcePHPIPAMSection(),
"phpipam_subnet": resourcePHPIPAMSubnet(),
"phpipam_vlan": resourcePHPIPAMVLAN(),
"phpipam_address": resourcePHPIPAMAddress(),
"phpipam_section": resourcePHPIPAMSection(),
"phpipam_subnet": resourcePHPIPAMSubnet(),
"phpipam_vlan": resourcePHPIPAMVLAN(),
"phpipam_first_free_address": resourcePHPIPAMFirstFreeAddress(),
},

DataSourcesMap: map[string]*schema.Resource{
Expand Down
180 changes: 180 additions & 0 deletions plugin/providers/phpipam/resource_phpipam_first_free_address.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package phpipam

import (
"errors"
"fmt"
"strconv"

"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)

// resourcePHPIPAMAddress returns the resource structure for the phpipam_address
// resource.
//
// Note that we use the data source read function here to pull down data, as
// read workflow is identical for both the resource and the data source.
func resourcePHPIPAMFirstFreeAddress() *schema.Resource {
return &schema.Resource{
Create: resourcePHPIPAMFirstFreeAddressCreate,
Read: dataSourcePHPIPAMAddressRead,
Update: resourcePHPIPAMFirstFreeAddressUpdate,
Delete: resourcePHPIPAMFirstFreeAddressDelete,
Schema: resourceFirstFreeAddressSchema(),
}
}

// bareAddressSchema returns a map[string]*schema.Schema with the schema used
// to represent a PHPIPAM address resource. This output should then be modified
// so that required and computed fields are set properly for both the data
// source and the resource.
func bareFirstFreeAddressSchema() map[string]*schema.Schema {
return map[string]*schema.Schema{
"address_id": &schema.Schema{
Type: schema.TypeInt,
},
"subnet_id": &schema.Schema{
Type: schema.TypeInt,
},
"ip_address": &schema.Schema{
Type: schema.TypeString,
},
"is_gateway": &schema.Schema{
Type: schema.TypeBool,
},
"description": &schema.Schema{
Type: schema.TypeString,
},
"hostname": &schema.Schema{
Type: schema.TypeString,
},
"mac_address": &schema.Schema{
Type: schema.TypeString,
},
"owner": &schema.Schema{
Type: schema.TypeString,
},
"state_tag_id": &schema.Schema{
Type: schema.TypeInt,
},
"skip_ptr_record": &schema.Schema{
Type: schema.TypeBool,
},
"ptr_record_id": &schema.Schema{
Type: schema.TypeInt,
},
"device_id": &schema.Schema{
Type: schema.TypeInt,
},
"switch_port_label": &schema.Schema{
Type: schema.TypeString,
},
"note": &schema.Schema{
Type: schema.TypeString,
},
"last_seen": &schema.Schema{
Type: schema.TypeString,
},
"exclude_ping": &schema.Schema{
Type: schema.TypeBool,
},
"edit_date": &schema.Schema{
Type: schema.TypeString,
},
"custom_fields": &schema.Schema{
Type: schema.TypeMap,
},
}
}

// resourceAddressSchema returns the schema for the phpipam_address resource.
// It sets the required and optional fields, the latter defined in
// resourceAddressRequiredFields, and ensures that all optional and
// non-configurable fields are computed as well.
func resourceFirstFreeAddressSchema() map[string]*schema.Schema {
s := bareAddressSchema()
for k, v := range s {
switch {
// IP Address and Subnet ID are ForceNew
case k == "subnet_id":
v.Required = true
v.ForceNew = true
case k == "custom_fields":
v.Optional = true
case resourceAddressOptionalFields.Has(k):
v.Optional = true
v.Computed = true
default:
v.Computed = true
}
}
return s
}

func resourcePHPIPAMFirstFreeAddressCreate(d *schema.ResourceData, meta interface{}) error {
// Get first free IP from provided subnet_id
subnet_id := d.Get("subnet_id").(int)
d.Set("subnet_id", nil)

// Get address controller and start address creation
c := meta.(*ProviderPHPIPAMClient).addressesController

in := expandAddress(d)

out, err := c.CreateFirstFreeAddress(subnet_id, in)
if err != nil {
return err
}
d.Set("ip_address", out)

// If we have custom fields, set them now. We need to get the IP address's ID
// beforehand.
if customFields, ok := d.GetOk("custom_fields"); ok {
addrs, err := c.GetAddressesByIP(out)
if err != nil {
return fmt.Errorf("Could not read IP address after creating: %s", err)
}
//addrs := d.Get("ip_address")

if len(addrs) != 1 {
return errors.New("IP address either missing or multiple results returned by reading IP after creation")
}

d.SetId(strconv.Itoa(addrs[0].ID))

if _, err := c.UpdateAddressCustomFields(addrs[0].ID, customFields.(map[string]interface{})); err != nil {
return err
}
}

return dataSourcePHPIPAMAddressRead(d, meta)
}

func resourcePHPIPAMFirstFreeAddressUpdate(d *schema.ResourceData, meta interface{}) error {
c := meta.(*ProviderPHPIPAMClient).addressesController
in := expandAddress(d)

// IPAddress and SubnetID need to be removed for update requests.
in.IPAddress = ""
in.SubnetID = 0
if _, err := c.UpdateAddress(in); err != nil {
return err
}

if err := updateCustomFields(d, c); err != nil {
return err
}

return dataSourcePHPIPAMAddressRead(d, meta)
}

func resourcePHPIPAMFirstFreeAddressDelete(d *schema.ResourceData, meta interface{}) error {
c := meta.(*ProviderPHPIPAMClient).addressesController
in := expandAddress(d)

// if _, err := c.DeleteAddress(in.ID, phpipam.BoolIntString(d.Get("remove_dns_on_delete").(bool))); err != nil {
if _, err := c.DeleteAddress(in.ID, false); err != nil {
return err
}
d.SetId("")
return nil
}