Skip to content

Commit

Permalink
Merge pull request #86 from nosportugal/master
Browse files Browse the repository at this point in the history
Use nest custom fields (subnets)
  • Loading branch information
pavel-z1 authored May 15, 2024
2 parents 87402a2 + 3750307 commit e8e7cb6
Show file tree
Hide file tree
Showing 9 changed files with 174 additions and 112 deletions.
14 changes: 9 additions & 5 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,12 @@ along to the configuration for a VM, say, for example, a

```hcl
provider "phpipam" {
app_id = "test"
endpoint = "https://phpipam.example.com/api"
password = "PHPIPAM_PASSWORD"
username = "Admin"
insecure = false
app_id = "test"
endpoint = "https://phpipam.example.com/api"
password = "PHPIPAM_PASSWORD"
username = "Admin"
insecure = false
nest_custom_fields = false
}
data "phpipam_subnet" "subnet" {
Expand Down Expand Up @@ -109,6 +110,9 @@ The options for the plugin are as follows:
supplied via the `PHPIPAM_USER_NAME` variable.
- `insecure` - Set to true to not validate the HTTPS certificate chain.
Optional parameter, can be used only with HTTPS connections
- `nest_custom_fields` - Set to true if the API application has this feature
enabled. Currently, this allows the provider to only make one API call when
creating subnets, instead of two.

### Resource importing

Expand Down
17 changes: 12 additions & 5 deletions plugin/providers/phpipam/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ type Config struct {

// Allow connect to HTTPS without SSL issuer validation
Insecure bool

// Whether the API client is configured to nest custom fields
NestCustomFields bool
}

// ProviderPHPIPAMClient is a structure that contains the client connections
Expand All @@ -57,16 +60,19 @@ type ProviderPHPIPAMClient struct {

// Mutex for free IP address allocation.
addressAllocationLock sync.Mutex

// Whether the API client is configured to nest custom values
NestCustomFields bool
}

// Client configures and returns a fully initialized PingdomClient.
func (c *Config) Client() (interface{}, error) {
cfg := phpipam.Config{
AppID: c.AppID,
Endpoint: c.Endpoint,
Password: c.Password,
Username: c.Username,
Insecure: c.Insecure,
AppID: c.AppID,
Endpoint: c.Endpoint,
Password: c.Password,
Username: c.Username,
Insecure: c.Insecure,
}
log.Printf("[DEBUG] Initializing PHPIPAM controllers")
sess := session.NewSession(cfg)
Expand All @@ -78,6 +84,7 @@ func (c *Config) Client() (interface{}, error) {
l2domainsController: l2domains.NewController(sess),
subnetsController: subnets.NewController(sess),
vlansController: vlans.NewController(sess),
NestCustomFields: c.NestCustomFields,
}

// Validate that our conneciton is okay
Expand Down
55 changes: 35 additions & 20 deletions plugin/providers/phpipam/data_source_phpipam_subnet.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
package phpipam

import (
"context"
"errors"
"fmt"
"strconv"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/pavel-z1/phpipam-sdk-go/controllers/subnets"
)

func dataSourcePHPIPAMSubnet() *schema.Resource {
return &schema.Resource{
Read: dataSourcePHPIPAMSubnetRead,
Schema: dataSourceSubnetSchema(),
ReadContext: dataSourcePHPIPAMSubnetRead,
Schema: dataSourceSubnetSchema(),
}
}

func dataSourcePHPIPAMSubnetRead(d *schema.ResourceData, meta interface{}) error {
func dataSourcePHPIPAMSubnetRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
c := meta.(*ProviderPHPIPAMClient).subnetsController
out := make([]subnets.Subnet, 1)
var err error
Expand All @@ -26,22 +28,22 @@ func dataSourcePHPIPAMSubnetRead(d *schema.ResourceData, meta interface{}) error
case d.Get("subnet_id").(int) != 0:
out[0], err = c.GetSubnetByID(d.Get("subnet_id").(int))
if err != nil {
return err
return diag.FromErr(err)
}
case d.Get("subnet_address").(string) != "" && d.Get("subnet_mask").(int) != 0 && d.Get("section_id").(int) == 0:
out, err = c.GetSubnetsByCIDR(fmt.Sprintf("%s/%d", d.Get("subnet_address"), d.Get("subnet_mask")))
if err != nil {
return err
return diag.FromErr(err)
}
case d.Get("subnet_address").(string) != "" && d.Get("subnet_mask").(int) != 0 && d.Get("section_id").(int) != 0:
out, err = c.GetSubnetsByCIDRAndSection(fmt.Sprintf("%s/%d", d.Get("subnet_address"), d.Get("subnet_mask")), d.Get("section_id").(int))
if err != nil {
return err
return diag.FromErr(err)
}
case d.Get("section_id").(int) != 0 && (d.Get("description").(string) != "" || d.Get("description_match").(string) != "" || len(d.Get("custom_field_filter").(map[string]interface{})) > 0):
out, err = subnetSearchInSection(d, meta)
if err != nil {
return err
return diag.FromErr(err)
}
default:
// We need to ensure imported resources are not recreated when terraform apply is ran
Expand All @@ -50,35 +52,48 @@ func dataSourcePHPIPAMSubnetRead(d *schema.ResourceData, meta interface{}) error
if len(id) > 0 {
subnet_id, err := strconv.Atoi(id)
if err != nil {
return err
return diag.FromErr(err)
}
out[0], err = c.GetSubnetByID(subnet_id)
if err != nil {
return err
return diag.FromErr(err)
}
} else {
return errors.New("No valid combination of parameters found - need one of subnet_id, subnet_address and subnet_mask, or section_id and (description|description_match|custom_field_filter)")
return diag.FromErr(errors.New("No valid combination of parameters found - need one of subnet_id, subnet_address and subnet_mask, or section_id and (description|description_match|custom_field_filter)"))
}
}
if len(out) != 1 {
return errors.New("Your search returned zero or multiple results. Please correct your search and try again")
return diag.FromErr(errors.New("Your search returned zero or multiple results. Please correct your search and try again"))
}

if checkSubnetsCustomFiledsExists(d, c) {
fields, err := c.GetSubnetCustomFields(out[0].ID)
switch {
case err == nil:
trimMap(fields)
if err := d.Set("custom_fields", fields); err != nil {
return err
if !meta.(*ProviderPHPIPAMClient).NestCustomFields {
if checkSubnetsCustomFiledsExists(d, c) {
fields, err := c.GetSubnetCustomFields(out[0].ID)
switch {
case err == nil:
trimMap(fields)
if err := d.Set("custom_fields", fields); err != nil {
return diag.FromErr(err)
}
case err != nil:
return diag.FromErr(err)
}
case err != nil:
return err
}
}

flattenSubnet(out[0], d)

if out[0].CustomFields != nil && !meta.(*ProviderPHPIPAMClient).NestCustomFields {

var diags diag.Diagnostics
diags = append(diags, diag.Diagnostic{
Severity: diag.Warning,
Summary: "Nest custom fields is enabled on the API",
Detail: "This API has enabled nested custom fields. Please set nest_custom_fields to true in the provider configuration.",
})
return diags
}

return nil
}

Expand Down
19 changes: 14 additions & 5 deletions plugin/providers/phpipam/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ func Provider() *schema.Provider {
Default: false,
Description: descriptions["insecure"],
},
"nest_custom_fields": {
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: descriptions["nest_custom_fields"],
},
},

ResourcesMap: map[string]*schema.Resource{
Expand Down Expand Up @@ -76,16 +82,19 @@ func init() {
"username": "The username of the PHPIPAM account",
"insecure": "Whether server should be accessed " +
"without verifying the TLS certificate.",
"nest_custom_fields": "Whether the API client is configured " +
"to nest custom values.",
}
}

func providerConfigure(d *schema.ResourceData) (interface{}, error) {
config := Config{
AppID: d.Get("app_id").(string),
Endpoint: d.Get("endpoint").(string),
Password: d.Get("password").(string),
Username: d.Get("username").(string),
Insecure: d.Get("insecure").(bool),
AppID: d.Get("app_id").(string),
Endpoint: d.Get("endpoint").(string),
Password: d.Get("password").(string),
Username: d.Get("username").(string),
Insecure: d.Get("insecure").(bool),
NestCustomFields: d.Get("nest_custom_fields").(bool),
}
return config.Client()
}
68 changes: 37 additions & 31 deletions plugin/providers/phpipam/resource_phpipam_first_free_subnet.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package phpipam

import (
"context"
"errors"
"fmt"
"strconv"

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

Expand All @@ -15,79 +17,83 @@ import (
// read workflow is identical for both the resource and the data source.
func resourcePHPIPAMFirstFreeSubnet() *schema.Resource {
return &schema.Resource{
Create: resourcePHPIPAMFirstFreeSubnetCreate,
Read: dataSourcePHPIPAMSubnetRead,
Update: resourcePHPIPAMFirstFreeSubnetUpdate,
Delete: resourcePHPIPAMFirstFreeSubnetDelete,
Schema: resourceFirstFreeSubnetSchema(),
CreateContext: resourcePHPIPAMFirstFreeSubnetCreate,
ReadContext: dataSourcePHPIPAMSubnetRead,
UpdateContext: resourcePHPIPAMFirstFreeSubnetUpdate,
DeleteContext: resourcePHPIPAMFirstFreeSubnetDelete,
Schema: resourceFirstFreeSubnetSchema(),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
}
}

func resourcePHPIPAMFirstFreeSubnetCreate(d *schema.ResourceData, meta interface{}) error {
func resourcePHPIPAMFirstFreeSubnetCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
// Get first free subnet from provided subnet_id
subnet_id := d.Get("parent_subnet_id").(int)
d.Set("subnet_id", nil)
subnet_mask := d.Get("subnet_mask").(int)
// Get address controller and start address creation
c := meta.(*ProviderPHPIPAMClient).subnetsController

in := expandSubnet(d)
in := expandSubnet(d, meta.(*ProviderPHPIPAMClient).NestCustomFields)

out, err := c.CreateFirstFreeSubnet(subnet_id, subnet_mask, in)
if err != nil {
return err
return diag.FromErr(err)
}
d.Set("subnet_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.GetSubnetsByCIDR(fmt.Sprintf("%s/%d", out, in.Mask))
if err != nil {
return fmt.Errorf("Could not read IP address after creating: %s", err)
}
if !meta.(*ProviderPHPIPAMClient).NestCustomFields {
// 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.GetSubnetsByCIDR(fmt.Sprintf("%s/%d", out, in.Mask))
if err != nil {
return diag.FromErr(fmt.Errorf("Could not read IP address after creating: %s", err))
}

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

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

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

return dataSourcePHPIPAMSubnetRead(d, meta)
return dataSourcePHPIPAMSubnetRead(ctx, d, meta)
}

func resourcePHPIPAMFirstFreeSubnetUpdate(d *schema.ResourceData, meta interface{}) error {
func resourcePHPIPAMFirstFreeSubnetUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
c := meta.(*ProviderPHPIPAMClient).subnetsController
in := expandSubnet(d)
in := expandSubnet(d, meta.(*ProviderPHPIPAMClient).NestCustomFields)

// SubnetAddress and mask need to be removed for update requests.
in.SubnetAddress = ""
in.Mask = 0
if _, err := c.UpdateSubnet(in); err != nil {
return err
return diag.FromErr(err)
}

if err := updateCustomFields(d, c); err != nil {
return err
if !meta.(*ProviderPHPIPAMClient).NestCustomFields {
if err := updateCustomFields(d, c); err != nil {
return diag.FromErr(err)
}
}

return dataSourcePHPIPAMSubnetRead(d, meta)
return dataSourcePHPIPAMSubnetRead(ctx, d, meta)
}

func resourcePHPIPAMFirstFreeSubnetDelete(d *schema.ResourceData, meta interface{}) error {
func resourcePHPIPAMFirstFreeSubnetDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
c := meta.(*ProviderPHPIPAMClient).subnetsController
in := expandSubnet(d)
in := expandSubnet(d, meta.(*ProviderPHPIPAMClient).NestCustomFields)

if _, err := c.DeleteSubnet(in.ID); err != nil {
return err
return diag.FromErr(err)
}
d.SetId("")
return nil
Expand Down
Loading

0 comments on commit e8e7cb6

Please sign in to comment.