Skip to content

Commit

Permalink
Merge terraform provider tidydns (#4)
Browse files Browse the repository at this point in the history
* Merge functionality from terraform-provider-tidydns upstream

* Code cleanup

* Cleanup comment

* Close HTTP response bodies
  • Loading branch information
jenrik authored Dec 2, 2024
1 parent a8bd589 commit d80f420
Show file tree
Hide file tree
Showing 3 changed files with 458 additions and 25 deletions.
254 changes: 229 additions & 25 deletions pkg/tidydns/tidydns.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,20 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"strings"
)

type TidyDNSClient interface {
GetSubnetIDs(ctx context.Context, subnetCIDR string) (*SubnetIDs, error)
GetFreeIP(ctx context.Context, subnetID int) (string, error)
CreateDHCPInterface(ctx context.Context, createInfo CreateInfo) (int, error)
ReadDHCPInterface(ctx context.Context, interfaceID int) (*InterfaceInfo, error)
UpdateDHCPInterfaceName(ctx context.Context, interfaceID int, interfaceName string) (int, error)
DeleteDHCPInterface(ctx context.Context, interfaceID int) error
ListZones(ctx context.Context) ([]*ZoneInfo, error)
FindZoneID(ctx context.Context, name string) (int, error)
CreateRecord(ctx context.Context, zoneID int, info RecordInfo) (int, error)
Expand Down Expand Up @@ -42,6 +49,7 @@ type CreateInfo struct {
ZoneID int
InterfaceIP string
InterfaceName string
LocationID int
}

type RecordInfo struct {
Expand All @@ -59,6 +67,7 @@ type LocationID int
type RecordType int
type RecordStatus int

//goland:noinspection GoUnusedConst
const (
RecordStatusActive RecordStatus = 0
RecordStatusInactive RecordStatus = 1
Expand Down Expand Up @@ -93,6 +102,206 @@ func New(baseURL, username, password string) TidyDNSClient {
}
}

func (c *tidyDNSClient) GetSubnetIDs(ctx context.Context, subnetCIDR string) (*SubnetIDs, error) {
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/=/dhcp_subnet?subnet=%s", c.baseURL, subnetCIDR), nil)
if err != nil {
return nil, err
}

req.SetBasicAuth(c.username, c.password)

res, err := c.client.Do(req)
if err != nil {
return nil, err
}
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(res.Body)
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("error from tidyDNS server: %s", res.Status)
}

var subnets []dhcpSubnet
err = json.NewDecoder(res.Body).Decode(&subnets)
if err != nil {
return nil, err
}

if len(subnets) == 0 {
return nil, fmt.Errorf("subnet not found: %s", subnetCIDR)
}

if len(subnets) > 1 {
return nil, fmt.Errorf("too many subnets found: %s", subnetCIDR)
}

return &SubnetIDs{
SubnetID: subnets[0].ID,
ZoneID: subnets[0].ZoneID,
VlanNo: subnets[0].VlanNo,
}, nil
}

func (c *tidyDNSClient) GetFreeIP(ctx context.Context, subnetID int) (string, error) {
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/=/dhcp_subnet_free_ip/%d", c.baseURL, subnetID), nil)
if err != nil {
return "", err
}

req.SetBasicAuth(c.username, c.password)

res, err := c.client.Do(req)
if err != nil {
return "", err
}
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(res.Body)
if res.StatusCode != http.StatusOK {
return "", fmt.Errorf("error from tidyDNS server: %s", res.Status)
}

var freeIP dhcpFreeIP
err = json.NewDecoder(res.Body).Decode(&freeIP)
if err != nil {
return "", err
}

return freeIP.Data.IPAddress, nil
}

func (c *tidyDNSClient) CreateDHCPInterface(ctx context.Context, createInfo CreateInfo) (int, error) {
data := url.Values{
"subnet_id": {strconv.Itoa(createInfo.SubnetID)},
"zone_id": {strconv.Itoa(createInfo.ZoneID)},
"name": {createInfo.InterfaceName},
"destination": {createInfo.InterfaceIP},
"location_id": {strconv.Itoa(createInfo.LocationID)},
}

var checkstring = fmt.Sprintf("Key (destination)=(%s) already exists", createInfo.InterfaceIP)

req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s/=/dhcp_interface//new", c.baseURL), strings.NewReader(data.Encode()))
if err != nil {
return 0, err
}
req.SetBasicAuth(c.username, c.password)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

res, err := c.client.Do(req)
if err != nil {
return 0, err
}
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(res.Body)

if res.StatusCode != http.StatusOK {
bodyBytes, err := io.ReadAll(res.Body)
bodyString := string(bodyBytes)
if err != nil {
return 0, err
}
if strings.Contains(bodyString, checkstring) {
return 1, fmt.Errorf("try again")
}
return 0, fmt.Errorf("error from tidyDNS server: %s", res.Status)
}

var createResp interfaceCreate
err = json.NewDecoder(res.Body).Decode(&createResp)
if err != nil {
return 0, err
}

return createResp.ID, nil
}

func (c *tidyDNSClient) ReadDHCPInterface(ctx context.Context, interfaceID int) (*InterfaceInfo, error) {
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/=/dhcp_interface/?id=%d", c.baseURL, interfaceID), nil)
if err != nil {
return nil, err
}

req.SetBasicAuth(c.username, c.password)

res, err := c.client.Do(req)
if err != nil {
return nil, err
}
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(res.Body)
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("error from tidyDNS server: %s", res.Status)
}

var interfaceRead interfaceRead
err = json.NewDecoder(res.Body).Decode(&interfaceRead)
if err != nil {
return nil, err
}

return &InterfaceInfo{
InterfaceIP: interfaceRead.Destination,
Interfacename: interfaceRead.Name,
}, nil
}

func (c *tidyDNSClient) UpdateDHCPInterfaceName(ctx context.Context, interfaceID int, interfaceName string) (int, error) {
data := url.Values{
"name": {interfaceName},
}

req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s/=/dhcp_interface//%d", c.baseURL, interfaceID), strings.NewReader(data.Encode()))
if err != nil {
return 0, err
}
req.SetBasicAuth(c.username, c.password)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

res, err := c.client.Do(req)
if err != nil {
return 0, err
}
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(res.Body)
if res.StatusCode != http.StatusOK {
return 0, fmt.Errorf("error from tidyDNS server: %s", res.Status)
}

var createResp interfaceCreate
err = json.NewDecoder(res.Body).Decode(&createResp)
if err != nil {
return 0, err
}

return createResp.ID, nil
}

func (c *tidyDNSClient) DeleteDHCPInterface(ctx context.Context, interfaceID int) error {
req, err := http.NewRequestWithContext(ctx, "DELETE", fmt.Sprintf("%s/=/dhcp_interface/%d", c.baseURL, interfaceID), nil)
if err != nil {
return err
}

req.SetBasicAuth(c.username, c.password)

res, err := c.client.Do(req)
if err != nil {
return err
}
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(res.Body)
if res.StatusCode != http.StatusOK {
return fmt.Errorf("error from tidyDNS server: %s", res.Status)
}

return nil
}

func (c *tidyDNSClient) ListZones(ctx context.Context) ([]*ZoneInfo, error) {
var zones []zoneInfo
err := c.getData(ctx, fmt.Sprintf("%s/=/zone?type=json", c.baseURL), &zones)
Expand Down Expand Up @@ -149,13 +358,12 @@ func (c *tidyDNSClient) CreateRecord(ctx context.Context, zoneID int, info Recor
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

res, err := c.client.Do(req)
if res != nil {
defer res.Body.Close()
}

if err != nil {
if err != nil || res == nil {
return 0, err
}
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(res.Body)
if res.StatusCode != http.StatusOK {
return 0, fmt.Errorf("error from tidyDNS server: %s", res.Status)
}
Expand All @@ -168,13 +376,12 @@ func (c *tidyDNSClient) CreateRecord(ctx context.Context, zoneID int, info Recor
req.SetBasicAuth(c.username, c.password)

res, err = c.client.Do(req)
if res != nil {
defer res.Body.Close()
}

if err != nil {
if err != nil || res == nil {
return 0, err
}
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(res.Body)
if res.StatusCode != http.StatusOK {
return 0, fmt.Errorf("error from tidyDNS server: %s", res.Status)
}
Expand Down Expand Up @@ -211,13 +418,12 @@ func (c *tidyDNSClient) UpdateRecord(ctx context.Context, zoneID int, recordID i
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

res, err := c.client.Do(req)
if res != nil {
defer res.Body.Close()
}

if err != nil {
if err != nil || res == nil {
return err
}
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(res.Body)
if res.StatusCode != http.StatusOK {
return fmt.Errorf("error from tidyDNS server: %s", res.Status)
}
Expand Down Expand Up @@ -299,13 +505,12 @@ func (c *tidyDNSClient) DeleteRecord(ctx context.Context, zoneID int, recordID i
req.SetBasicAuth(c.username, c.password)

res, err := c.client.Do(req)
if res != nil {
defer res.Body.Close()
}

if err != nil {
if err != nil || res == nil {
return err
}
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(res.Body)
if res.StatusCode != http.StatusOK {
return fmt.Errorf("error from tidyDNS server: %s", res.Status)
}
Expand All @@ -322,13 +527,12 @@ func (c *tidyDNSClient) getData(ctx context.Context, url string, value interface
req.SetBasicAuth(c.username, c.password)

res, err := c.client.Do(req)
if res != nil {
defer res.Body.Close()
}

if err != nil {
if err != nil || res == nil {
return err
}
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(res.Body)
if res.StatusCode != http.StatusOK {
return fmt.Errorf("error from tidyDNS server: %s", res.Status)
}
Expand Down
Loading

0 comments on commit d80f420

Please sign in to comment.