From fb63c2b9a34d0bf3220b4410da70712fec6a19c9 Mon Sep 17 00:00:00 2001 From: Samson Gudise Date: Sat, 27 Oct 2018 11:35:46 -0600 Subject: [PATCH 1/5] Checkout FirstFree Subnet --- .../data_source_phpipam_first_free_subnet.go | 43 +++++++++ plugin/providers/phpipam/provider.go | 2 + .../resource_phpipam_first_free_subnet.go | 96 +++++++++++++++++++ plugin/providers/phpipam/subnet_structure.go | 25 +++++ .../controllers/subnets/subnets.go | 15 ++- .../phpipam-sdk-go/phpipam/request/request.go | 27 +++++- 6 files changed, 204 insertions(+), 4 deletions(-) create mode 100755 plugin/providers/phpipam/data_source_phpipam_first_free_subnet.go create mode 100755 plugin/providers/phpipam/resource_phpipam_first_free_subnet.go diff --git a/plugin/providers/phpipam/data_source_phpipam_first_free_subnet.go b/plugin/providers/phpipam/data_source_phpipam_first_free_subnet.go new file mode 100755 index 0000000..fc6bab8 --- /dev/null +++ b/plugin/providers/phpipam/data_source_phpipam_first_free_subnet.go @@ -0,0 +1,43 @@ +package phpipam + +import ( + "fmt" + "errors" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourcePHPIPAMFirstFreeSubnet() *schema.Resource { + return &schema.Resource{ + Read: dataSourcePHPIPAMFirstFreeSubnetRead, + Schema: map[string]*schema.Schema{ + "master_subnet_id": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + "subnet_mask": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + "subnet_address": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourcePHPIPAMFirstFreeSubnetRead(d *schema.ResourceData, meta interface{}) error { + c := meta.(*ProviderPHPIPAMClient).subnetsController + out, err := c.GetFirstFreeSubnet(d.Get("master_subnet_id").(int),d.Get("subnet_mask").(int)) + if err != nil { + return err + } + if out == "" { + return errors.New(fmt.Sprintf("Master Subnet has no free subnet of size %s", d.Get("subnet_mask"))) + } + + d.SetId(out) + d.Set("subnet_address", out) + + return nil +} diff --git a/plugin/providers/phpipam/provider.go b/plugin/providers/phpipam/provider.go index e36322f..86d1466 100644 --- a/plugin/providers/phpipam/provider.go +++ b/plugin/providers/phpipam/provider.go @@ -40,12 +40,14 @@ func Provider() terraform.ResourceProvider { "phpipam_section": resourcePHPIPAMSection(), "phpipam_subnet": resourcePHPIPAMSubnet(), "phpipam_vlan": resourcePHPIPAMVLAN(), + "phpipam_first_free_subnet": resourcePHPIPAMFirstFreeSubnet(), }, DataSourcesMap: map[string]*schema.Resource{ "phpipam_address": dataSourcePHPIPAMAddress(), "phpipam_addresses": dataSourcePHPIPAMAddresses(), "phpipam_first_free_address": dataSourcePHPIPAMFirstFreeAddress(), + "phpipam_first_free_subnet": dataSourcePHPIPAMFirstFreeSubnet(), "phpipam_section": dataSourcePHPIPAMSection(), "phpipam_subnet": dataSourcePHPIPAMSubnet(), "phpipam_subnets": dataSourcePHPIPAMSubnets(), diff --git a/plugin/providers/phpipam/resource_phpipam_first_free_subnet.go b/plugin/providers/phpipam/resource_phpipam_first_free_subnet.go new file mode 100755 index 0000000..012ece0 --- /dev/null +++ b/plugin/providers/phpipam/resource_phpipam_first_free_subnet.go @@ -0,0 +1,96 @@ +package phpipam + +import ( + "fmt" + "strings" + "strconv" + "errors" + "github.com/hashicorp/terraform/helper/schema" +) + +// resourcePHPIPAMSubnet returns the resource structure for the phpipam_subnet +// 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 resourcePHPIPAMFirstFreeSubnet() *schema.Resource { + return &schema.Resource{ + Create: resourcePHPIPAMFirstFreeSubnetCreate, + Read: dataSourcePHPIPAMSubnetRead, + Update: resourcePHPIPAMSubnetUpdate, + Delete: resourcePHPIPAMSubnetDelete, + Schema: resourceFirstFreeSubnetSchema(), + } +} + +func resourcePHPIPAMFirstFreeSubnetCreate(d *schema.ResourceData, meta interface{}) error { + c := meta.(*ProviderPHPIPAMClient).subnetsController + + id := d.Get("master_subnet_id").(int) + mask := d.Get("subnet_mask").(int) + + message, err := c.CreateFirstFreeSubnet(id,mask); + if err != nil { + return err + } + cidr_mask := strings.Split(message, "/"); + d.Set("subnet_address", cidr_mask[0]) + if customFields, ok := d.GetOk("custom_fields"); ok { + subnets, err := c.GetSubnetsByCIDR(fmt.Sprintf("%s/%s", cidr_mask[0], cidr_mask[1])) + + if err != nil { + return fmt.Errorf("Could not read subnet after creating: %s", err) + } + + if len(subnets) != 1 { + return errors.New("Subnet either missing or multiple results returned by reading subnet after creation") + } + + d.SetId(strconv.Itoa(subnets[0].ID)) + d.Set("subnet_id", subnets[0].ID) + d.Set("subnet_address", subnets[0].SubnetAddress) + d.Set("subnet_mask", subnets[0].Mask) + if _, err := c.UpdateSubnetCustomFields(subnets[0].ID, customFields.(map[string]interface{})); err != nil { + return err + } + } + return dataSourcePHPIPAMSubnetRead(d, meta) +} + +// flattenSubnet(out[0], d) + + // in.SetId(strconv.Itoa(subnet.ID)) + // If we have custom fields, set them now. We need to get the subnet's ID + // beforehand. + + +// func resourcePHPIPAMSubnetUpdate(d *schema.ResourceData, meta interface{}) error { +// c := meta.(*ProviderPHPIPAMClient).subnetsController +// in := expandSubnet(d) +// // Remove the CIDR fields from the request, as these fields being present +// // implies that the subnet will be either split or renamed, which is not +// // supported by UpdateSubnet. These are implemented in the API but not in the +// // SDK, so support may be added at a later time. +// in.SubnetAddress = "" +// in.Mask = 0 +// if _, err := c.UpdateSubnet(in); err != nil { +// return err +// } + +// if err := updateCustomFields(d, c); err != nil { +// return err +// } + +// return dataSourcePHPIPAMSubnetRead(d, meta) +// } + +// func resourcePHPIPAMFirstChildSubnetDelete(d *schema.ResourceData, meta interface{}) error { +// c := meta.(*ProviderPHPIPAMClient).subnetsController +// in := expandSubnet(d) + +// if _, err := c.DeleteSubnet(in.ID); err != nil { +// return err +// } +// d.SetId("") +// return nil +// } diff --git a/plugin/providers/phpipam/subnet_structure.go b/plugin/providers/phpipam/subnet_structure.go index ee59bab..bc3fc20 100644 --- a/plugin/providers/phpipam/subnet_structure.go +++ b/plugin/providers/phpipam/subnet_structure.go @@ -114,6 +114,31 @@ func bareSubnetSchema() map[string]*schema.Schema { } } +// resourceSubnetSchema returns the schema for the phpipam_first_free_subnet resource. It +// sets the required and optional fields, the latter defined in +// resourceSubnetRequiredFields, and ensures that all optional and +// non-configurable fields are computed as well. +func resourceFirstFreeSubnetSchema() map[string]*schema.Schema { + schema := bareSubnetSchema() + for k, v := range schema { + switch { + // Subnet Address and Mask are currently ForceNew + case k == "master_subnet_id" || k == "subnet_mask" : + v.Required = true + v.ForceNew = true + case k == "custom_fields": + v.Optional = true + case resourceSubnetOptionalFields.Has(k): + v.Optional = true + v.Computed = true + default: + v.Computed = true + } + } + return schema +} + + // resourceSubnetSchema returns the schema for the phpipam_subnet resource. It // sets the required and optional fields, the latter defined in // resourceSubnetRequiredFields, and ensures that all optional and diff --git a/vendor/github.com/paybyphone/phpipam-sdk-go/controllers/subnets/subnets.go b/vendor/github.com/paybyphone/phpipam-sdk-go/controllers/subnets/subnets.go index 9f972a3..367bd1a 100644 --- a/vendor/github.com/paybyphone/phpipam-sdk-go/controllers/subnets/subnets.go +++ b/vendor/github.com/paybyphone/phpipam-sdk-go/controllers/subnets/subnets.go @@ -4,7 +4,6 @@ package subnets import ( "fmt" - "github.com/paybyphone/phpipam-sdk-go/controllers/addresses" "github.com/paybyphone/phpipam-sdk-go/phpipam" "github.com/paybyphone/phpipam-sdk-go/phpipam/client" @@ -122,7 +121,19 @@ func (c *Controller) GetSubnetsByCIDR(cidr string) (out []Subnet, err error) { return } -// GetFirstFreeAddress GETs the first free IP address in a subnet and returns +// Create new child subnet inside subnet with specified mask. +func (c *Controller) CreateFirstFreeSubnet(id, mask int) (out string, err error) { + err = c.SendRequest("POST", fmt.Sprintf("/subnets/%d/first_subnet/%d", id, mask),&struct{}{}, &out) + return +} + +// Get first free/available subnet from master subnet +func (c *Controller) GetFirstFreeSubnet(id, mask int) (out string, err error) { + err = c.SendRequest("GET", fmt.Sprintf("/subnets/%d/first_subnet/%d", id, mask),&struct{}{}, &out) + return +} + +// GetFirstFreeAddress GETs the free IP address in a subnet and returns // it as a string. This can be used to automatically determine the next address // you should use. If there are no more available addresses, the string will be // blank. diff --git a/vendor/github.com/paybyphone/phpipam-sdk-go/phpipam/request/request.go b/vendor/github.com/paybyphone/phpipam-sdk-go/phpipam/request/request.go index d8f47dd..a6258ea 100644 --- a/vendor/github.com/paybyphone/phpipam-sdk-go/phpipam/request/request.go +++ b/vendor/github.com/paybyphone/phpipam-sdk-go/phpipam/request/request.go @@ -28,6 +28,21 @@ type APIResponse struct { Success bool } +type APIResponseInt struct { + // The HTTP result code. + Code int + + // The response data. This is further unmarshaled into the data type set by + // Request.Output. + Data json.RawMessage + + // The error message, if the request failed. + Message string + + // Whether or not the API request was successful. + Success int +} + // Request represents the API request. type Request struct { // The API session. @@ -76,10 +91,18 @@ func (r *requestResponse) BodyString() string { // request is successful and the response data is unmarshalled. func (r *requestResponse) ReadResponseJSON(v interface{}) error { var resp APIResponse + var respi APIResponseInt if err := json.Unmarshal(r.Body, &resp); err != nil { - return fmt.Errorf("JSON parsing error: %s - Response body: %s", err, r.Body) - } + if err := json.Unmarshal(r.Body, &respi); err != nil { + return fmt.Errorf("JSON parsing error: %s - Response body: %s", err, r.Body) + } else { + if (respi.Success == 0) { resp.Success = true } else { resp.Success = false } + resp.Code = respi.Code + resp.Data = respi.Data + resp.Message = respi.Message + } + } if !resp.Success { return r.handleError() } From 2f0a22afd43147af1c10e1ccf7583e5fbd0be068 Mon Sep 17 00:00:00 2001 From: Samson Gudise Date: Tue, 30 Oct 2018 15:40:50 -0600 Subject: [PATCH 2/5] README updates --- README.md | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/README.md b/README.md index 9e32757..a9a5230 100644 --- a/README.md +++ b/README.md @@ -402,6 +402,35 @@ The following attributes are exported: listing. * `dns_resolver_id` - The ID of the DNS resolver to use in the section. +#### The `phpipam_first_free_subnet` Data Source +The `phpipam_first_free_subnet` data source get the first available `subnet_address` from given `master_subnet_id` and `subnet_mask` + +**Example:** +``` +data "phpipam_first_free_subnet" "subnet" { + master_subnet_id = "9" + subnet_mask = "26" +} + +output subnet_cidr_block { + value = "${data.phpipam_first_free_subnet.subnet.subnet_address}" +} +``` + +##### Argument Reference + +The data source takes the following parameters: +* `master_subnet_id` (Required) - The ID of the parent subnet for this subnet +* `subnet_mask` (Required) - The subnet mask, in bits. + +##### Attribute Reference + +The following attributes are exported: + + * `subnet_address` - The network address of the subnet + subnet. + + #### The `phpipam_subnet` Data Source The `phpipam_subnet` data source gets information on a subnet such as its ID @@ -425,6 +454,7 @@ resource "phpipam_address" { } ``` + **Example with `description_match`:** ``` @@ -823,6 +853,44 @@ The following attributes are exported: * `section_id` - The ID of the section in the PHPIPAM database. * `edit_date` - The date this resource was last edited. +#### The `phpipam_first_free_subnet` Resource +The `phpipam_first_free_subnet` resouce can be used to checkout and manage a subnet in PHPIPAM. It is same as creating subnet using `phpipam_subnet` resource, with an option to checkout first free subnet from given `master_subnet_id` and `subnet_mask` + +Example: +``` +resource "phpipam_first_free_subnet" "subnet" { + master_subnet_id = 9 + subnet_mask = 24 + custom_fields = { + CustomTestSubnets = "terraform-test" + } + +} + +``` +##### Argument Reference + +The resouce takes following parameters: +* `master_subnet_id` (Required) - The ID of the parent subnet for this subnet +* `subnet_mask` (Required) - The subnet mask, in bits. + +⚠️ **NOTE on custom fields:** PHPIPAM installations with custom fields must have +all fields set to optional when using this plugin. For more info see +[here](https://github.com/phpipam/phpipam/issues/1073). Further to this, either +ensure that your fields also do not have default values, or ensure the default +is set in your TF configuration. Diff loops may happen otherwise! + +##### Attribute Reference + +The following attributes are exported: + + * `subnet_id` - The ID of the subnet in the PHPIPAM database. + * `subnet_address` - The network address of the subnet + * `permissions` - A JSON representation of the permissions associated with this + subnet. + * `edit_date` - The date this resource was last updated. + + #### The `phpipam_subnet` Resource The `phpipam_subnet` resource can be used to create and manage a subnet in From f6d7b603da141abbfbf06cbc526404e84ffd00fd Mon Sep 17 00:00:00 2001 From: Samson Gudise Date: Wed, 31 Oct 2018 09:34:09 -0600 Subject: [PATCH 3/5] circleci --- .circleci/config.yml | 41 +++++++++++++++++++++++++++++++++++++++++ README.md | 4 ++-- 2 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..a8eb584 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,41 @@ +version: 2 +jobs: + build: + docker: + - image: dockercore/golang-cross:1.9.5 + environment: + http_proxy: http://proxy-infra.frg.tech:3128 + https_proxy: http://proxy-infra.frg.tech:3128 + no_proxy: 127.0.0.1,repo.frg.tech + ARTIFACTORY_URL: http://repo.frg.tech/artifactory/cloud/ + AWS_DEFAULT_REGION: us-east-1 + + steps: + - checkout + - run: + name: Config Environment + command: | + echo 'Config Environment' > $BASH_ENV + - run: + name: test + command: | + make test + - deploy: + name: deploy + command: | + make build + ARTIFACT_NAME="terraform-provider-phpipam" + curl -fSs -u${artifactory_user_id}:${artifactory_password} -T ./pkg/darwin_amd64/${ARTIFACT_NAME} "${ARTIFACTORY_URL}/${CIRCLE_PROJECT_REPONAME}/pkg/darwin_amd64/${ARTIFACT_NAME};branch=${CIRCLE_BRANCH}" + curl -fSs -u${artifactory_user_id}:${artifactory_password} -T ./pkg/linux_amd64/${ARTIFACT_NAME} "${ARTIFACTORY_URL}/${CIRCLE_PROJECT_REPONAME}/pkg/linux_amd64/${ARTIFACT_NAME};branch=${CIRCLE_BRANCH}" + curl -fSs -u${artifactory_user_id}:${artifactory_password} -T ./pkg/windows_amd64/${ARTIFACT_NAME} "${ARTIFACTORY_URL}/${CIRCLE_PROJECT_REPONAME}/pkg/windows_amd64/${ARTIFACT_NAME};branch=${CIRCLE_BRANCH}" + # python -m kioskbucket > template.json + # if [[ ${CIRCLE_BRANCH} == "master" ]]; then VERSION=1.0.${CIRCLE_BUILD_NUM} + # else VERSION=1.0-${CIRCLE_BRANCH}-${CIRCLE_BUILD_NUM} + # fi; + # TEMPLATE_DIR=${CIRCLE_PROJECT_REPONAME}-${VERSION}; + # mkdir ${TEMPLATE_DIR}; + # mv template.json ${TEMPLATE_DIR}/; + # ARTIFACT_NAME=${TEMPLATE_DIR}.tgz; + # tar cvzf ${ARTIFACT_NAME} ${TEMPLATE_DIR}; + # curl -fSs -u${artifactory_user_id}:${artifactory_password} -T ./${ARTIFACT_NAME} "${ARTIFACTORY_URL}/${CIRCLE_PROJECT_REPONAME}/${ARTIFACT_NAME};branch=${CIRCLE_BRANCH}" + diff --git a/README.md b/README.md index a9a5230..edd59cc 100644 --- a/README.md +++ b/README.md @@ -403,7 +403,7 @@ The following attributes are exported: * `dns_resolver_id` - The ID of the DNS resolver to use in the section. #### The `phpipam_first_free_subnet` Data Source -The `phpipam_first_free_subnet` data source get the first available `subnet_address` from given `master_subnet_id` and `subnet_mask` +The `phpipam_first_free_subnet` data source returns first available subnet within given `master_subnet_id` for specified `subnet_mask` **Example:** ``` @@ -421,7 +421,7 @@ output subnet_cidr_block { The data source takes the following parameters: * `master_subnet_id` (Required) - The ID of the parent subnet for this subnet -* `subnet_mask` (Required) - The subnet mask, in bits. +* `subnet_mask` (Required) - The subnet mask of first available subnet, in bits. ##### Attribute Reference From 256c7b2e1e28462681f01baa8d45669d91d4a4e3 Mon Sep 17 00:00:00 2001 From: Samson Gudise Date: Thu, 1 Nov 2018 16:15:20 -0600 Subject: [PATCH 4/5] makefile updated --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 05845b6..6b8454e 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ test: testacc: TF_ACC=1 go test -v ./plugin/providers/phpipam -run="TestAcc" -build: deps +build: gox -osarch="linux/amd64 windows/amd64 darwin/amd64" \ -output="pkg/{{.OS}}_{{.Arch}}/terraform-provider-phpipam" . From 1c03b1780168596030b0db17e5b9cc4e72f3dd75 Mon Sep 17 00:00:00 2001 From: Samson Gudise Date: Thu, 15 Nov 2018 17:16:52 -0700 Subject: [PATCH 5/5] deleted commented and unwanted code --- .circleci/config.yml | 41 ------------------- Makefile | 2 +- .../resource_phpipam_first_free_subnet.go | 40 +----------------- 3 files changed, 2 insertions(+), 81 deletions(-) delete mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index a8eb584..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,41 +0,0 @@ -version: 2 -jobs: - build: - docker: - - image: dockercore/golang-cross:1.9.5 - environment: - http_proxy: http://proxy-infra.frg.tech:3128 - https_proxy: http://proxy-infra.frg.tech:3128 - no_proxy: 127.0.0.1,repo.frg.tech - ARTIFACTORY_URL: http://repo.frg.tech/artifactory/cloud/ - AWS_DEFAULT_REGION: us-east-1 - - steps: - - checkout - - run: - name: Config Environment - command: | - echo 'Config Environment' > $BASH_ENV - - run: - name: test - command: | - make test - - deploy: - name: deploy - command: | - make build - ARTIFACT_NAME="terraform-provider-phpipam" - curl -fSs -u${artifactory_user_id}:${artifactory_password} -T ./pkg/darwin_amd64/${ARTIFACT_NAME} "${ARTIFACTORY_URL}/${CIRCLE_PROJECT_REPONAME}/pkg/darwin_amd64/${ARTIFACT_NAME};branch=${CIRCLE_BRANCH}" - curl -fSs -u${artifactory_user_id}:${artifactory_password} -T ./pkg/linux_amd64/${ARTIFACT_NAME} "${ARTIFACTORY_URL}/${CIRCLE_PROJECT_REPONAME}/pkg/linux_amd64/${ARTIFACT_NAME};branch=${CIRCLE_BRANCH}" - curl -fSs -u${artifactory_user_id}:${artifactory_password} -T ./pkg/windows_amd64/${ARTIFACT_NAME} "${ARTIFACTORY_URL}/${CIRCLE_PROJECT_REPONAME}/pkg/windows_amd64/${ARTIFACT_NAME};branch=${CIRCLE_BRANCH}" - # python -m kioskbucket > template.json - # if [[ ${CIRCLE_BRANCH} == "master" ]]; then VERSION=1.0.${CIRCLE_BUILD_NUM} - # else VERSION=1.0-${CIRCLE_BRANCH}-${CIRCLE_BUILD_NUM} - # fi; - # TEMPLATE_DIR=${CIRCLE_PROJECT_REPONAME}-${VERSION}; - # mkdir ${TEMPLATE_DIR}; - # mv template.json ${TEMPLATE_DIR}/; - # ARTIFACT_NAME=${TEMPLATE_DIR}.tgz; - # tar cvzf ${ARTIFACT_NAME} ${TEMPLATE_DIR}; - # curl -fSs -u${artifactory_user_id}:${artifactory_password} -T ./${ARTIFACT_NAME} "${ARTIFACTORY_URL}/${CIRCLE_PROJECT_REPONAME}/${ARTIFACT_NAME};branch=${CIRCLE_BRANCH}" - diff --git a/Makefile b/Makefile index 6b8454e..05845b6 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ test: testacc: TF_ACC=1 go test -v ./plugin/providers/phpipam -run="TestAcc" -build: +build: deps gox -osarch="linux/amd64 windows/amd64 darwin/amd64" \ -output="pkg/{{.OS}}_{{.Arch}}/terraform-provider-phpipam" . diff --git a/plugin/providers/phpipam/resource_phpipam_first_free_subnet.go b/plugin/providers/phpipam/resource_phpipam_first_free_subnet.go index 012ece0..68add78 100755 --- a/plugin/providers/phpipam/resource_phpipam_first_free_subnet.go +++ b/plugin/providers/phpipam/resource_phpipam_first_free_subnet.go @@ -55,42 +55,4 @@ func resourcePHPIPAMFirstFreeSubnetCreate(d *schema.ResourceData, meta interface } } return dataSourcePHPIPAMSubnetRead(d, meta) -} - -// flattenSubnet(out[0], d) - - // in.SetId(strconv.Itoa(subnet.ID)) - // If we have custom fields, set them now. We need to get the subnet's ID - // beforehand. - - -// func resourcePHPIPAMSubnetUpdate(d *schema.ResourceData, meta interface{}) error { -// c := meta.(*ProviderPHPIPAMClient).subnetsController -// in := expandSubnet(d) -// // Remove the CIDR fields from the request, as these fields being present -// // implies that the subnet will be either split or renamed, which is not -// // supported by UpdateSubnet. These are implemented in the API but not in the -// // SDK, so support may be added at a later time. -// in.SubnetAddress = "" -// in.Mask = 0 -// if _, err := c.UpdateSubnet(in); err != nil { -// return err -// } - -// if err := updateCustomFields(d, c); err != nil { -// return err -// } - -// return dataSourcePHPIPAMSubnetRead(d, meta) -// } - -// func resourcePHPIPAMFirstChildSubnetDelete(d *schema.ResourceData, meta interface{}) error { -// c := meta.(*ProviderPHPIPAMClient).subnetsController -// in := expandSubnet(d) - -// if _, err := c.DeleteSubnet(in.ID); err != nil { -// return err -// } -// d.SetId("") -// return nil -// } +} \ No newline at end of file