diff --git a/CHANGELOG.md b/CHANGELOG.md index d1d4840f..f3380470 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ FEATURES: * **New Data Source:** `proxmox_virtual_environment_hosts` * **New Resource:** `proxmox_virtual_environment_certificate` * **New Resource:** `proxmox_virtual_environment_dns` +* **New Resource:** `proxmox_virtual_environment_hosts` ENHANCEMENTS: diff --git a/README.md b/README.md index 61da7c8b..ea128146 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ A Terraform Provider which adds support for Proxmox solutions. - [DNS](#dns-proxmox_virtual_environment_dns-1) - [File](#file-proxmox_virtual_environment_file) - [Group](#group-proxmox_virtual_environment_group-1) + - [Hosts](#hosts-proxmox_virtual_environment_hosts-1) - [Pool](#pool-proxmox_virtual_environment_pool-1) - [Role](#role-proxmox_virtual_environment_role-1) - [User](#user-proxmox_virtual_environment_user-1) @@ -155,7 +156,7 @@ This data source doesn't accept arguments. ###### Attributes * `addresses` - The IP addresses * `digest` - The SHA1 digest -* `entries` - The entries (conversion of `addresses` and `hostnames` into objects) +* `entries` - The host entries (conversion of `addresses` and `hostnames` into objects) * `hostnames` - The hostnames associated with each of the IP addresses ##### Nodes (proxmox_virtual_environment_nodes) @@ -341,6 +342,20 @@ You must ensure that you have at least `Size-in-MB * 2 + 1` MB of storage space ###### Attributes * `members` - The group members as a list with `username@realm` entries +##### Hosts (proxmox_virtual_environment_hosts) + +###### Arguments +* `node_name` - (Required) A node name +* `entry` - (Required) A host entry (multiple blocks supported) + * `address` - (Required) The IP address + * `hostnames` - (Required) The hostnames + +###### Attributes +* `addresses` - The IP addresses +* `digest` - The SHA1 digest +* `entries` - The host entries (conversion of `addresses` and `hostnames` into objects) +* `hostnames` - The hostnames associated with each of the IP addresses + ##### Pool (proxmox_virtual_environment_pool) ###### Arguments diff --git a/example/resource_virtual_environment_hosts.tf b/example/resource_virtual_environment_hosts.tf new file mode 100644 index 00000000..77b59ead --- /dev/null +++ b/example/resource_virtual_environment_hosts.tf @@ -0,0 +1,28 @@ +resource "proxmox_virtual_environment_hosts" "example" { + node_name = "${data.proxmox_virtual_environment_nodes.example.names[0]}" + + dynamic "entry" { + for_each = "${data.proxmox_virtual_environment_hosts.example.entries}" + + content { + address = "${entry.value.address}" + hostnames = "${entry.value.hostnames}" + } + } +} + +output "resource_proxmox_virtual_environment_hosts_example_addresses" { + value = "${proxmox_virtual_environment_hosts.example.addresses}" +} + +output "resource_proxmox_virtual_environment_hosts_example_digest" { + value = "${proxmox_virtual_environment_hosts.example.digest}" +} + +output "resource_proxmox_virtual_environment_hosts_example_entries" { + value = "${proxmox_virtual_environment_hosts.example.entries}" +} + +output "resource_proxmox_virtual_environment_hosts_example_hostnames" { + value = "${proxmox_virtual_environment_hosts.example.hostnames}" +} diff --git a/proxmoxtf/data_source_virtual_environment_hosts.go b/proxmoxtf/data_source_virtual_environment_hosts.go index efb5e98b..c8e90495 100644 --- a/proxmoxtf/data_source_virtual_environment_hosts.go +++ b/proxmoxtf/data_source_virtual_environment_hosts.go @@ -37,7 +37,7 @@ func dataSourceVirtualEnvironmentHosts() *schema.Resource { }, mkDataSourceVirtualEnvironmentHostsEntries: &schema.Schema{ Type: schema.TypeList, - Description: "The entries", + Description: "The host entries", Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ diff --git a/proxmoxtf/provider.go b/proxmoxtf/provider.go index 26a82ff5..2b07db75 100644 --- a/proxmoxtf/provider.go +++ b/proxmoxtf/provider.go @@ -49,6 +49,7 @@ func Provider() *schema.Provider { "proxmox_virtual_environment_dns": resourceVirtualEnvironmentDNS(), "proxmox_virtual_environment_file": resourceVirtualEnvironmentFile(), "proxmox_virtual_environment_group": resourceVirtualEnvironmentGroup(), + "proxmox_virtual_environment_hosts": resourceVirtualEnvironmentHosts(), "proxmox_virtual_environment_pool": resourceVirtualEnvironmentPool(), "proxmox_virtual_environment_role": resourceVirtualEnvironmentRole(), "proxmox_virtual_environment_user": resourceVirtualEnvironmentUser(), diff --git a/proxmoxtf/resource_virtual_environment_hosts.go b/proxmoxtf/resource_virtual_environment_hosts.go new file mode 100644 index 00000000..5cbd0440 --- /dev/null +++ b/proxmoxtf/resource_virtual_environment_hosts.go @@ -0,0 +1,229 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +package proxmoxtf + +import ( + "fmt" + "strings" + + "github.com/danitso/terraform-provider-proxmox/proxmox" + "github.com/hashicorp/terraform/helper/schema" +) + +const ( + mkResourceVirtualEnvironmentHostsAddresses = "addresses" + mkResourceVirtualEnvironmentHostsDigest = "digest" + mkResourceVirtualEnvironmentHostsEntries = "entries" + mkResourceVirtualEnvironmentHostsEntriesAddress = "address" + mkResourceVirtualEnvironmentHostsEntriesHostnames = "hostnames" + mkResourceVirtualEnvironmentHostsEntry = "entry" + mkResourceVirtualEnvironmentHostsEntryAddress = "address" + mkResourceVirtualEnvironmentHostsEntryHostnames = "hostnames" + mkResourceVirtualEnvironmentHostsHostnames = "hostnames" + mkResourceVirtualEnvironmentHostsNodeName = "node_name" +) + +func resourceVirtualEnvironmentHosts() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentHostsAddresses: &schema.Schema{ + Type: schema.TypeList, + Description: "The addresses", + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + mkResourceVirtualEnvironmentHostsDigest: &schema.Schema{ + Type: schema.TypeString, + Description: "The SHA1 digest", + Computed: true, + }, + mkResourceVirtualEnvironmentHostsEntries: &schema.Schema{ + Type: schema.TypeList, + Description: "The host entries", + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentHostsEntriesAddress: { + Type: schema.TypeString, + Description: "The address", + Computed: true, + }, + mkResourceVirtualEnvironmentHostsEntriesHostnames: &schema.Schema{ + Type: schema.TypeList, + Description: "The hostnames", + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, + mkResourceVirtualEnvironmentHostsEntry: &schema.Schema{ + Type: schema.TypeList, + Description: "The host entries", + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentHostsEntryAddress: { + Type: schema.TypeString, + Description: "The address", + Required: true, + }, + mkResourceVirtualEnvironmentHostsEntryHostnames: &schema.Schema{ + Type: schema.TypeList, + Description: "The hostnames", + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + MinItems: 1, + }, + }, + }, + }, + mkResourceVirtualEnvironmentHostsHostnames: &schema.Schema{ + Type: schema.TypeList, + Description: "The hostnames", + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + mkResourceVirtualEnvironmentHostsNodeName: &schema.Schema{ + Type: schema.TypeString, + Description: "The node name", + Required: true, + }, + }, + Create: resourceVirtualEnvironmentHostsCreate, + Read: resourceVirtualEnvironmentHostsRead, + Update: resourceVirtualEnvironmentHostsUpdate, + Delete: resourceVirtualEnvironmentHostsDelete, + } +} + +func resourceVirtualEnvironmentHostsCreate(d *schema.ResourceData, m interface{}) error { + err := resourceVirtualEnvironmentHostsUpdate(d, m) + + if err != nil { + return err + } + + nodeName := d.Get(mkResourceVirtualEnvironmentHostsNodeName).(string) + + d.SetId(fmt.Sprintf("%s_hosts", nodeName)) + + return nil +} + +func resourceVirtualEnvironmentHostsRead(d *schema.ResourceData, m interface{}) error { + config := m.(providerConfiguration) + veClient, err := config.GetVEClient() + + if err != nil { + return err + } + + nodeName := d.Get(mkResourceVirtualEnvironmentHostsNodeName).(string) + hosts, err := veClient.GetHosts(nodeName) + + if err != nil { + return err + } + + // Parse the entries in the hosts file. + addresses := []interface{}{} + entries := []interface{}{} + hostnames := []interface{}{} + lines := strings.Split(hosts.Data, "\n") + + for _, line := range lines { + if strings.HasPrefix(line, "#") { + continue + } + + line = strings.ReplaceAll(line, "\t", " ") + values := strings.Split(line, " ") + + if values[0] == "" { + continue + } + + addresses = append(addresses, values[0]) + entry := map[string]interface{}{} + hostnamesForAddress := []interface{}{} + + for _, hostname := range values[1:] { + if hostname != "" { + hostnamesForAddress = append(hostnamesForAddress, hostname) + } + } + + entry[mkResourceVirtualEnvironmentHostsEntriesAddress] = values[0] + entry[mkResourceVirtualEnvironmentHostsEntriesHostnames] = hostnamesForAddress + + entries = append(entries, entry) + hostnames = append(hostnames, hostnamesForAddress) + } + + d.Set(mkResourceVirtualEnvironmentHostsAddresses, addresses) + + if hosts.Digest != nil { + d.Set(mkResourceVirtualEnvironmentHostsDigest, *hosts.Digest) + } else { + d.Set(mkResourceVirtualEnvironmentHostsDigest, "") + } + + d.Set(mkResourceVirtualEnvironmentHostsEntries, entries) + d.Set(mkResourceVirtualEnvironmentHostsEntry, entries) + d.Set(mkResourceVirtualEnvironmentHostsHostnames, hostnames) + + return nil +} + +func resourceVirtualEnvironmentHostsUpdate(d *schema.ResourceData, m interface{}) error { + config := m.(providerConfiguration) + veClient, err := config.GetVEClient() + + if err != nil { + return err + } + + entry := d.Get(mkResourceVirtualEnvironmentHostsEntry).([]interface{}) + nodeName := d.Get(mkResourceVirtualEnvironmentHostsNodeName).(string) + + // Generate the data for the hosts file based on the specified entries. + body := proxmox.VirtualEnvironmentHostsUpdateRequestBody{ + Data: "", + } + + for _, e := range entry { + eMap := e.(map[string]interface{}) + + address := eMap[mkResourceVirtualEnvironmentHostsEntryAddress].(string) + hostnames := eMap[mkResourceVirtualEnvironmentHostsEntryHostnames].([]interface{}) + + body.Data += address + + for _, h := range hostnames { + hostname := h.(string) + body.Data += " " + hostname + } + + body.Data += "\n" + } + + err = veClient.UpdateHosts(nodeName, &body) + + if err != nil { + return err + } + + return resourceVirtualEnvironmentHostsRead(d, m) +} + +func resourceVirtualEnvironmentHostsDelete(d *schema.ResourceData, m interface{}) error { + d.SetId("") + + return nil +} diff --git a/proxmoxtf/resource_virtual_environment_hosts_test.go b/proxmoxtf/resource_virtual_environment_hosts_test.go new file mode 100644 index 00000000..5bde7b85 --- /dev/null +++ b/proxmoxtf/resource_virtual_environment_hosts_test.go @@ -0,0 +1,83 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +package proxmoxtf + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/schema" +) + +// TestResourceVirtualEnvironmentHostsInstantiation tests whether the ResourceVirtualEnvironmentHosts instance can be instantiated. +func TestResourceVirtualEnvironmentHostsInstantiation(t *testing.T) { + s := resourceVirtualEnvironmentHosts() + + if s == nil { + t.Fatalf("Cannot instantiate resourceVirtualEnvironmentHosts") + } +} + +// TestResourceVirtualEnvironmentHostsSchema tests the resourceVirtualEnvironmentHosts schema. +func TestResourceVirtualEnvironmentHostsSchema(t *testing.T) { + s := resourceVirtualEnvironmentHosts() + + testRequiredArguments(t, s, []string{ + mkResourceVirtualEnvironmentHostsEntry, + mkResourceVirtualEnvironmentHostsNodeName, + }) + + testComputedAttributes(t, s, []string{ + mkResourceVirtualEnvironmentHostsAddresses, + mkResourceVirtualEnvironmentHostsDigest, + mkResourceVirtualEnvironmentHostsEntries, + mkResourceVirtualEnvironmentHostsHostnames, + }) + + testSchemaValueTypes(t, s, []string{ + mkResourceVirtualEnvironmentHostsAddresses, + mkResourceVirtualEnvironmentHostsDigest, + mkResourceVirtualEnvironmentHostsEntries, + mkResourceVirtualEnvironmentHostsEntry, + mkResourceVirtualEnvironmentHostsHostnames, + mkResourceVirtualEnvironmentHostsNodeName, + }, []schema.ValueType{ + schema.TypeList, + schema.TypeString, + schema.TypeList, + schema.TypeList, + schema.TypeList, + schema.TypeString, + }) + + entriesSchema := testNestedSchemaExistence(t, s, mkResourceVirtualEnvironmentHostsEntries) + + testComputedAttributes(t, entriesSchema, []string{ + mkResourceVirtualEnvironmentHostsEntriesAddress, + mkResourceVirtualEnvironmentHostsEntriesHostnames, + }) + + testSchemaValueTypes(t, entriesSchema, []string{ + mkResourceVirtualEnvironmentHostsEntriesAddress, + mkResourceVirtualEnvironmentHostsEntriesHostnames, + }, []schema.ValueType{ + schema.TypeString, + schema.TypeList, + }) + + entrySchema := testNestedSchemaExistence(t, s, mkResourceVirtualEnvironmentHostsEntry) + + testRequiredArguments(t, entrySchema, []string{ + mkResourceVirtualEnvironmentHostsEntryAddress, + mkResourceVirtualEnvironmentHostsEntryHostnames, + }) + + testSchemaValueTypes(t, entrySchema, []string{ + mkResourceVirtualEnvironmentHostsEntryAddress, + mkResourceVirtualEnvironmentHostsEntryHostnames, + }, []schema.ValueType{ + schema.TypeString, + schema.TypeList, + }) +}