Skip to content

Commit

Permalink
Workaround for IP allocation bug in mreg
Browse files Browse the repository at this point in the history
  • Loading branch information
oyvindhagberg committed Nov 18, 2024
1 parent 2f81494 commit 5994cd5
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 52 deletions.
27 changes: 15 additions & 12 deletions examples/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,20 @@ resource "mreg_hosts" "my_hosts" {
}
host {
name = "terraform-provider-test02.example.com"
# You can also manually pick an ip address instead of getting assigned a free one
manual_ipaddress = "192.168.0.55"
# You can also manually pick an ip address instead of being assigned one
ipv4 = "192.168.0.55"
}
host {
name = "terraform-provider-test03.example.com"
}
contact = "your.email.address@example.com"
comment = "Created by the Terraform provider for Mreg"
network = "192.168.0.0/16"
policies = "without_monitoring, backup_no_backup"
network = ["192.168.0.0/16"]
policies = ["without_monitoring", "backup_no_backup"]
}

locals {
hostnames = toset(["test01.terraform-provider-test.example.com", "test02.terraform-provider-test.example.com"])
hostnames = toset(["terraform-provider-test04.example.com", "terraform-provider-test05.example.com"])
}

resource "mreg_hosts" "loop_hosts" {
Expand All @@ -43,16 +43,16 @@ resource "mreg_hosts" "loop_hosts" {
}
contact = "your.email.address@example.com"
comment = "Created by the Terraform provider for Mreg"
network = "192.168.0.0/16"
network = ["192.168.0.0/16"]
}

resource "mreg_hosts" "metahosts" {
# hosts without IP addresses
host {
name = "terraform-provider-meta01.example.com"
name = "terraform-provider-test06.example.com"
}
host {
name = "terraform-provider-meta02.example.com"
name = "terraform-provider-test07.example.com"
}
contact = "your.email.address@example.com"
comment = "Created by the Terraform provider for Mreg"
Expand All @@ -74,15 +74,18 @@ resource "mreg_dns_srv" "srv" {
# hosts with both IPv4 and IPv6
resource "mreg_hosts" "host_with_multiple_ips" {
host {
name = "terraform-provider-test04.example.com"
# this host will be assigned addresses from the ranges given in the network parameter
name = "terraform-provider-test08.example.com"
}
host {
name = "terraform-provider-test05.example.com"
manual_ipaddress = "192.168.0.243,fd12:3456:789a:1::1" # the manual_ipaddress field can have a comma-separated list of addresses
# this host will get the specified ip addresses
name = "terraform-provider-test09.example.com"
ipv4 = "192.168.0.243"
ipv6 = "fd12:3456:789a:1::1"
}
contact = "your.email.address@example.com"
comment = "Created by the Terraform provider for Mreg"
network = "192.168.0.0/16,fc00::/7" # the network field can have a comma-separated list of networks
network = ["192.168.0.0/16","fd00::/8"]
}

output "foo" {
Expand Down
5 changes: 3 additions & 2 deletions examples/run_example.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ go get
go build
#TODO after the provider is added to the registry, there's no need to copy the file here
rm -rf ~/.terraform.d/plugins/uio.no/usit/mreg/
mkdir -p ~/.terraform.d/plugins/uio.no/usit/mreg/0.1.5/linux_amd64
cp terraform-provider-mreg ~/.terraform.d/plugins/uio.no/usit/mreg/0.1.5/linux_amd64/
mkdir -p ~/.terraform.d/plugins/uio.no/usit/mreg/0.1.6/linux_amd64
cp terraform-provider-mreg ~/.terraform.d/plugins/uio.no/usit/mreg/0.1.6/linux_amd64/
popd >/dev/null
rm -rf .terraform .terraform.lock.hcl terraform.tfstate* crash.log
terraform init
#export TF_LOG=DEBUG
terraform apply -auto-approve -parallelism=10
terraform plan -detailed-exitcode # If there's a diff, the provider is not refreshing the state correctly
echo "Dropping you into a shell so you can inspect what was created... exit when ready"
Expand Down
5 changes: 0 additions & 5 deletions internal/provider/apiclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ import (
"encoding/json"
"fmt"
"io"
"math/rand"
"net/http"
"strconv"
"strings"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
Expand Down Expand Up @@ -38,9 +36,6 @@ func (apiClient apiClient) httpRequest(method, urlPath string, requestBody map[s

// Set up the request
url := apiClient.UrlWithoutSlash() + urlPath
if method == "GET" {
url += "?" + strconv.Itoa(rand.Int())
}
req, err := http.NewRequest(method, url, reqBodyReader)
if err != nil {
diags = diag.FromErr(err)
Expand Down
109 changes: 76 additions & 33 deletions internal/provider/resource_hosts.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@ import (
"context"
"crypto/md5"
"fmt"
"log"
"net/http"
"net/url"
"sort"
"strings"
"time"

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

var seen []string

func resourceHosts() *schema.Resource {
return &schema.Resource{
CreateContext: resourceHostsCreate,
Expand All @@ -39,20 +42,27 @@ func resourceHosts() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
"manual_ipaddress": &schema.Schema{
"ipv4": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"ipaddress": &schema.Schema{
"ipv6": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"ipaddress": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Computed: true,
},
},
},
},
"network": &schema.Schema{
Type: schema.TypeString,
"network": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
ForceNew: true,
},
Expand All @@ -66,16 +76,17 @@ func resourceHosts() *schema.Resource {
Required: true,
ForceNew: true,
},
"policies": &schema.Schema{
Type: schema.TypeString,
"policies": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
ForceNew: true,
},
},
}
}

func splitString(source string) []string {
/*func splitString(source string) []string {
result := make([]string, 0)
for _, s := range strings.Split(source, ",") {
s = strings.TrimSpace(s)
Expand All @@ -84,6 +95,16 @@ func splitString(source string) []string {
}
}
return result
}*/

func convertTerraformInputToListOfStrings(input interface{}) []string {
result := make([]string, 0)
a := input.([]interface{})
for _, elem := range a {
b := elem.(string)
result = append(result, b)
}
return result
}

func resourceHostsCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
Expand All @@ -94,8 +115,8 @@ func resourceHostsCreate(ctx context.Context, d *schema.ResourceData, m interfac
hosts := d.Get("host").([]interface{})
comment := d.Get("comment").(string)
contact := d.Get("contact").(string)
networks := splitString(d.Get("network").(string))
policies := splitString(d.Get("policies").(string))
networks := convertTerraformInputToListOfStrings(d.Get("network"))
policies := convertTerraformInputToListOfStrings(d.Get("policies"))

lock := fslock.New("terraform-provider-mreg-lockfile")
lock.Lock()
Expand All @@ -107,7 +128,14 @@ func resourceHostsCreate(ctx context.Context, d *schema.ResourceData, m interfac
hostname := host["name"].(string)
hostnames[i] = hostname

manual_ips := splitString(host["manual_ipaddress"].(string))
//TODO manual_ips := convertTerraformInputToListOfStrings(host["manual_ipaddress"])
manual_ips := make([]string, 0)
if s, ok := host["ipv4"].(string); ok && s != "" {
manual_ips = append(manual_ips, s)
}
if s, ok := host["ipv6"].(string); ok && s != "" {
manual_ips = append(manual_ips, s)
}

// Allocate a new host object in Mreg
postdata := map[string]interface{}{
Expand Down Expand Up @@ -147,11 +175,31 @@ func resourceHostsCreate(ctx context.Context, d *schema.ResourceData, m interfac
if len(manual_ips) == 0 {
for i := 1; i < len(networks); i++ {
// Find an unused address
_, body, diags := apiClient.httpRequest("GET", "/api/v1/networks/"+networks[i]+"/first_unused", nil, http.StatusOK)
if len(diags) > 0 {
return diags
var ipaddress string
retries := 0
for true {
_, body, diags := apiClient.httpRequest("GET", "/api/v1/networks/"+networks[i]+"/first_unused", nil, http.StatusOK)
if len(diags) > 0 {
return diags
}
ipaddress = body.(string)
actuallyUnused := true
for _, v := range seen {
if v == ipaddress {
actuallyUnused = false
break
}
}
if actuallyUnused {
seen = append(seen, ipaddress)
break
} else if retries > 100 {
log.Fatalf("Unable to find enough unused addresses on network %s", networks[i])
} else {
time.Sleep(time.Second)
retries++
}
}
ipaddress := body.(string)
postdata := map[string]interface{}{
"ipaddress": ipaddress,
"host": host_id,
Expand Down Expand Up @@ -180,26 +228,25 @@ func resourceHostsCreate(ctx context.Context, d *schema.ResourceData, m interfac
return diags
}
result = body.(map[string]interface{})
ipaddressesCommaSeparated := ""

ipaddresses := make([]string, 0)
for _, elem := range result["ipaddresses"].([]interface{}) {
m := elem.(map[string]interface{})
if ipaddressesCommaSeparated == "" {
ipaddressesCommaSeparated = m["ipaddress"].(string)
} else {
ipaddressesCommaSeparated = ipaddressesCommaSeparated + "," + m["ipaddress"].(string)
}
ipaddresses = append(ipaddresses, m["ipaddress"].(string))
}

// Update the ResourceData
host["ipaddress"] = ipaddressesCommaSeparated
host["ipaddress"] = ipaddresses
host["comment"] = comment
host["contact"] = contact
hosts[i] = host

d.Set("host", hosts)
d.SetId(hostname)
}
d.Set("host", hosts)
if err := d.Set("host", hosts); err != nil {
return diag.FromErr(err)
}
d.SetId(compoundId(hostnames))

return diags
Expand Down Expand Up @@ -233,27 +280,23 @@ func resourceHostsRead(ctx context.Context, d *schema.ResourceData, m interface{
}
result := body.(map[string]interface{})

// make a comma-separated list of the IP address(es) in case there are many
ipaddressesCommaSeparated := ""
ipaddresses := make([]string, 0)
for _, elem := range result["ipaddresses"].([]interface{}) {
m := elem.(map[string]interface{})
if ipaddressesCommaSeparated == "" {
ipaddressesCommaSeparated = m["ipaddress"].(string)
} else {
ipaddressesCommaSeparated = ipaddressesCommaSeparated + "," + m["ipaddress"].(string)
}
ipaddresses = append(ipaddresses, m["ipaddress"].(string))
}

// Update the data model with data from Mreg
host["comment"] = result["comment"]
host["ipaddress"] = ipaddressesCommaSeparated
host["ipaddress"] = ipaddresses
host["contact"] = result["contact"]
hosts[i] = host

hostnames = append(hostnames, hostname)
}

d.Set("host", hosts)
if err := d.Set("host", hosts); err != nil {
return diag.FromErr(err)
}
d.SetId(compoundId(hostnames))

return diag.Diagnostics{}
Expand Down

0 comments on commit 5994cd5

Please sign in to comment.