Skip to content

Commit

Permalink
A new feature lets you have multiple ips per host
Browse files Browse the repository at this point in the history
  • Loading branch information
oyvindhagberg committed Oct 31, 2024
1 parent d6d7178 commit c7b0606
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 67 deletions.
3 changes: 2 additions & 1 deletion .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Visit https://goreleaser.com for documentation on how to customize this
# behavior.
version: 1
before:
hooks:
# this is just an example and not a requirement for provider building/publishing
Expand Down Expand Up @@ -38,7 +39,7 @@ checksum:
signs:
- artifacts: checksum
args:
# if you are using this in a GitHub action or some other automated pipeline, you
# if you are using this in a GitHub action or some other automated pipeline, you
# need to pass the batch flag to indicate its not interactive.
- "--batch"
- "--local-user"
Expand Down
28 changes: 23 additions & 5 deletions examples/main.tf
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
terraform {
required_providers {
mreg = {
version = "0.1.5"
version = "0.1.6"
source = "uio.no/usit/mreg"
}
}
}

provider "mreg" {
serverurl = "https://mreg-test01.example.com/"
serverurl = "https://mreg-test.example.com/"
token = "1234567890ABCDEF" # replace with actual token
}

Expand All @@ -20,14 +20,14 @@ 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.0.2.55"
manual_ipaddress = "192.168.0.55"
}
host {
name = "terraform-provider-test03.example.com"
}
contact = "[email protected]"
comment = "Created by the Terraform provider for Mreg"
network = "192.0.2.0/24"
network = "192.168.0.0/16"
policies = "without_monitoring, backup_no_backup"
}

Expand All @@ -43,7 +43,7 @@ resource "mreg_hosts" "loop_hosts" {
}
contact = "[email protected]"
comment = "Created by the Terraform provider for Mreg"
network = "192.0.2.0/24"
network = "192.168.0.0/16"
}

resource "mreg_hosts" "metahosts" {
Expand Down Expand Up @@ -71,6 +71,20 @@ resource "mreg_dns_srv" "srv" {
port = 3306
}

# hosts with both IPv4 and IPv6
resource "mreg_hosts" "host_with_multiple_ips" {
host {
name = "terraform-provider-test04.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
}
contact = "[email protected]"
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
}

output "foo" {
value = mreg_hosts.my_hosts
}
Expand All @@ -82,3 +96,7 @@ output "bar" {
output "baz" {
value = mreg_dns_srv.srv
}

output "qux" {
value = mreg_hosts.host_with_multiple_ips
}
4 changes: 2 additions & 2 deletions examples/run_example.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ set -e
cd `dirname $0`
pushd .. >/dev/null
rm -f terraform-provider-mreg
go get -v
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/
popd >/dev/null
rm -rf .terraform .terraform.lock.hcl terraform.tfstate crash.log
rm -rf .terraform .terraform.lock.hcl terraform.tfstate* crash.log
terraform init
terraform apply -auto-approve -parallelism=10
terraform plan -detailed-exitcode # If there's a diff, the provider is not refreshing the state correctly
Expand Down
144 changes: 85 additions & 59 deletions internal/provider/resource_hosts.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"net/http"
"net/url"
"sort"
"strconv"
"strings"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
Expand Down Expand Up @@ -76,6 +75,17 @@ func resourceHosts() *schema.Resource {
}
}

func splitString(source string) []string {
result := make([]string, 0)
for _, s := range strings.Split(source, ",") {
s = strings.TrimSpace(s)
if s != "" {
result = append(result, s)
}
}
return result
}

func resourceHostsCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
var diags diag.Diagnostics

Expand All @@ -84,17 +94,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)
network := d.Get("network").(string)
policies_string := d.Get("policies").(string)
policies := make([]string, 0)
if policies_string != "" {
for _, s := range strings.Split(policies_string, ",") {
s = strings.TrimSpace(s)
if s != "" {
policies = append(policies, s)
}
}
}
networks := splitString(d.Get("network").(string))
policies := splitString(d.Get("policies").(string))

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

var ipaddress string

manual_ip := host["manual_ipaddress"].(string)
if manual_ip != "" {
ipaddress = manual_ip
} else {
if network != "" {
// Find a free IP address in Mreg
body, _, diags := apiClient.httpRequest(
"GET", fmt.Sprintf("/api/v1/networks/%s/first_unused", url.QueryEscape(network)),
nil, http.StatusOK)
if len(diags) > 0 {
return diags
}

ipaddress = strings.Trim(body, "\"")
}
}
manual_ips := splitString(host["manual_ipaddress"].(string))

// Allocate a new host object in Mreg
postdata := map[string]interface{}{
"name": hostname,
"contact": contact,
"comment": comment,
}
// Only add the ipaddress parameter if the host is supposed to have an IP address, or it will fail
if ipaddress != "" {
postdata["ipaddress"] = ipaddress
if len(manual_ips) > 0 {
postdata["ipaddress"] = manual_ips[0]
} else if len(networks) > 0 {
postdata["network"] = networks[0]
}
_, _, diags := apiClient.httpRequest("POST", "/api/v1/hosts/", postdata, http.StatusCreated)
if len(diags) > 0 {
return diags
}

// Retrieve the host by name to find Mreg's internal ID for the host
_, body, diags := apiClient.httpRequest("GET", "/api/v1/hosts/"+url.QueryEscape(hostname), nil, http.StatusOK)
if len(diags) > 0 {
return diags
}
result := body.(map[string]interface{})
host_id := int(result["id"].(float64))

// Assign any additional ip addresses
for i := 1; i < len(manual_ips); i++ {
postdata := map[string]interface{}{
"ipaddress": manual_ips[i],
"host": host_id,
}
_, _, diags := apiClient.httpRequest("POST", "/api/v1/ipaddresses/", postdata, http.StatusCreated)
if len(diags) > 0 {
return diags
}
}
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
}
ipaddress := body.(string)
postdata := map[string]interface{}{
"ipaddress": ipaddress,
"host": host_id,
}
_, _, diags = apiClient.httpRequest("POST", "/api/v1/ipaddresses/", postdata, http.StatusCreated)
if len(diags) > 0 {
return diags
}
}
}

// Assign host policies, if any
for _, p := range policies {
postdata := map[string]interface{}{
Expand All @@ -151,8 +174,24 @@ func resourceHostsCreate(ctx context.Context, d *schema.ResourceData, m interfac
}
}

// Retrieve information about the host to find out which ip addresses it ended up with
_, body, diags = apiClient.httpRequest("GET", "/api/v1/hosts/"+url.QueryEscape(hostname), nil, http.StatusOK)
if len(diags) > 0 {
return diags
}
result = body.(map[string]interface{})
ipaddressesCommaSeparated := ""
for _, elem := range result["ipaddresses"].([]interface{}) {
m := elem.(map[string]interface{})
if ipaddressesCommaSeparated == "" {
ipaddressesCommaSeparated = m["ipaddress"].(string)
} else {
ipaddressesCommaSeparated = ipaddressesCommaSeparated + "," + m["ipaddress"].(string)
}
}

// Update the ResourceData
host["ipaddress"] = ipaddress
host["ipaddress"] = ipaddressesCommaSeparated
host["comment"] = comment
host["contact"] = contact
hosts[i] = host
Expand Down Expand Up @@ -194,9 +233,20 @@ 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 := ""
for _, elem := range result["ipaddresses"].([]interface{}) {
m := elem.(map[string]interface{})
if ipaddressesCommaSeparated == "" {
ipaddressesCommaSeparated = m["ipaddress"].(string)
} else {
ipaddressesCommaSeparated = ipaddressesCommaSeparated + "," + m["ipaddress"].(string)
}
}

// Update the data model with data from Mreg
host["comment"] = result["comment"]
host["ipaddress"] = GetStringFromData(result, "ipaddresses.0.ipaddress")
host["ipaddress"] = ipaddressesCommaSeparated
host["contact"] = result["contact"]
hosts[i] = host

Expand Down Expand Up @@ -243,27 +293,3 @@ func compoundId(hostnames []string) string {
}
return fmt.Sprintf("%x", hash.Sum(nil))
}

// GetStringFromData lets you specify a path to the value that you want
// (e.g. "aaa.bbb.ccc") and have it extracted from the data structure.
func GetStringFromData(v interface{}, path string) string {
for _, key := range strings.Split(path, ".") {
iKey, err := strconv.ParseInt(key, 10, 32)
if err == nil {
// If the key is a number, we assume the structure is an array
arr, ok := v.([]interface{})
if !ok || int64(len(arr)) <= iKey {
return ""
}
v = arr[iKey]
} else {
// If the key isn't a number, we assume the structure is a map
m, ok := v.(map[string]interface{})
if !ok {
return ""
}
v = m[key]
}
}
return fmt.Sprintf("%v", v)
}

0 comments on commit c7b0606

Please sign in to comment.