Skip to content
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

Use nest custom fields (subnets) #86

Merged
merged 3 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 3 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,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 your PHPIPAM application has the nested
custom fields feature enabled. ptional parameter, currently only affects
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small typo
Please correct it to Optional parameter

subnet resources.

### 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