From 6f4b984f7080e7be6a8bcb0a699e09986089b0f9 Mon Sep 17 00:00:00 2001 From: Ionut Balutoiu Date: Thu, 24 Jun 2021 15:34:07 +0300 Subject: [PATCH 01/14] Set TF state only for computed fields However, make sure those fields are properly set when importing an existing resource. --- .gitignore | 1 + maas/resource_maas_machine.go | 32 +++++++++---------- maas/resource_maas_network_interface_link.go | 6 +--- ...esource_maas_network_interface_physical.go | 3 ++ maas/resource_maas_vm_host.go | 19 ++++++----- maas/resource_maas_vm_host_machine.go | 11 +------ 6 files changed, 33 insertions(+), 39 deletions(-) diff --git a/.gitignore b/.gitignore index aedcf3fd..e152a26b 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ website/node_modules *.backup ./*.tfstate .terraform.tfstate.lock.info +.terraform.lock.hcl .terraform/ *.log *.bak diff --git a/maas/resource_maas_machine.go b/maas/resource_maas_machine.go index 9f191365..54c3553d 100644 --- a/maas/resource_maas_machine.go +++ b/maas/resource_maas_machine.go @@ -26,6 +26,22 @@ func resourceMaasMachine() *schema.Resource { if err != nil { return nil, err } + if err := d.Set("power_type", machine.PowerType); err != nil { + return nil, err + } + powerParams, err := client.Machine.GetPowerParameters(machine.SystemID) + if err != nil { + return nil, err + } + if err := d.Set("power_parameters", powerParams); err != nil { + return nil, err + } + if err := d.Set("pxe_mac_address", machine.BootInterface.MACAddress); err != nil { + return nil, err + } + if err := d.Set("architecture", machine.Architecture); err != nil { + return nil, err + } d.SetId(machine.SystemID) return []*schema.ResourceData{d}, nil }, @@ -114,22 +130,6 @@ func resourceMachineRead(ctx context.Context, d *schema.ResourceData, m interfac } // Set Terraform state - if err := d.Set("power_type", machine.PowerType); err != nil { - return diag.FromErr(err) - } - powerParams, err := client.Machine.GetPowerParameters(machine.SystemID) - if err != nil { - return diag.FromErr(err) - } - if err := d.Set("power_parameters", powerParams); err != nil { - return diag.FromErr(err) - } - if err := d.Set("pxe_mac_address", machine.BootInterface.MACAddress); err != nil { - return diag.FromErr(err) - } - if err := d.Set("architecture", machine.Architecture); err != nil { - return diag.FromErr(err) - } if err := d.Set("min_hwe_kernel", machine.MinHWEKernel); err != nil { return diag.FromErr(err) } diff --git a/maas/resource_maas_network_interface_link.go b/maas/resource_maas_network_interface_link.go index d3325ab6..05f835df 100644 --- a/maas/resource_maas_network_interface_link.go +++ b/maas/resource_maas_network_interface_link.go @@ -5,7 +5,6 @@ import ( "fmt" "net" "strconv" - "strings" "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -54,7 +53,7 @@ func resourceMaasNetworkInterfaceLink() *schema.Resource { Type: schema.TypeString, Optional: true, ForceNew: true, - Default: "", + Computed: true, ValidateDiagFunc: func(value interface{}, path cty.Path) diag.Diagnostics { v := value.(string) if ip := net.ParseIP(v); ip == nil { @@ -110,9 +109,6 @@ func resourceNetworkInterfaceLinkRead(ctx context.Context, d *schema.ResourceDat } // Set the Terraform state - if err := d.Set("mode", strings.ToUpper(link.Mode)); err != nil { - return diag.FromErr(err) - } if err := d.Set("ip_address", link.IPAddress); err != nil { return diag.FromErr(err) } diff --git a/maas/resource_maas_network_interface_physical.go b/maas/resource_maas_network_interface_physical.go index 2038b1ce..b0401be4 100644 --- a/maas/resource_maas_network_interface_physical.go +++ b/maas/resource_maas_network_interface_physical.go @@ -49,6 +49,9 @@ func resourceMaasNetworkInterfacePhysical() *schema.Resource { if err := d.Set("mac_address", networkInterface.MACAddress); err != nil { return nil, err } + if err := d.Set("tags", networkInterface.Tags); err != nil { + return nil, err + } if err := d.Set("vlan", defaultVLAN); err != nil { return nil, err } diff --git a/maas/resource_maas_vm_host.go b/maas/resource_maas_vm_host.go index 9f1d9071..6229f759 100644 --- a/maas/resource_maas_vm_host.go +++ b/maas/resource_maas_vm_host.go @@ -57,6 +57,12 @@ func resourceMaasVMHost() *schema.Resource { if err := d.Set("power_address", vmHostParams.PowerAddress); err != nil { return nil, err } + if err := d.Set("power_user", vmHostParams.PowerUser); err != nil { + return nil, err + } + if err := d.Set("power_pass", vmHostParams.PowerPass); err != nil { + return nil, err + } } return []*schema.ResourceData{d}, nil }, @@ -212,6 +218,9 @@ func resourceVMHostRead(ctx context.Context, d *schema.ResourceData, m interface if err := d.Set("tags", vmHost.Tags); err != nil { return diag.FromErr(err) } + if err := d.Set("default_macvlan_mode", vmHost.DefaultMACVLANMode); err != nil { + return diag.FromErr(err) + } if err := d.Set("resources_cores_available", vmHost.Available.Cores); err != nil { return diag.FromErr(err) } @@ -317,8 +326,8 @@ func getVMHostCreateParams(d *schema.ResourceData) *entity.VMHostParams { func getVMHostUpdateParams(d *schema.ResourceData, vmHost *entity.VMHost, params *entity.VMHostParams) *entity.VMHostParams { params.Type = vmHost.Type params.Name = vmHost.Name - params.CPUOverCommitRatio = vmHost.CPUOverCommitRatio - params.MemoryOverCommitRatio = vmHost.MemoryOverCommitRatio + params.CPUOverCommitRatio = d.Get("cpu_over_commit_ratio").(float64) + params.MemoryOverCommitRatio = d.Get("memory_over_commit_ratio").(float64) params.DefaultMacvlanMode = vmHost.DefaultMACVLANMode params.Zone = vmHost.Zone.Name params.Pool = vmHost.Pool.Name @@ -342,12 +351,6 @@ func getVMHostUpdateParams(d *schema.ResourceData, vmHost *entity.VMHost, params if p, ok := d.GetOk("tags"); ok { params.Tags = strings.Join(convertToStringSlice(p.(*schema.Set).List()), ",") } - if p, ok := d.GetOk("cpu_over_commit_ratio"); ok { - params.CPUOverCommitRatio = p.(float64) - } - if p, ok := d.GetOk("memory_over_commit_ratio"); ok { - params.MemoryOverCommitRatio = p.(float64) - } if p, ok := d.GetOk("default_macvlan_mode"); ok { params.DefaultMacvlanMode = p.(string) } diff --git a/maas/resource_maas_vm_host_machine.go b/maas/resource_maas_vm_host_machine.go index b8a8d845..00138ec2 100644 --- a/maas/resource_maas_vm_host_machine.go +++ b/maas/resource_maas_vm_host_machine.go @@ -153,16 +153,7 @@ func resourceVMHostMachineCreate(ctx context.Context, d *schema.ResourceData, m return diag.FromErr(err) } - // Set Terraform state - if err := d.Set("cores", params.Cores); err != nil { - return diag.FromErr(err) - } - if err := d.Set("pinned_cores", params.PinnedCores); err != nil { - return diag.FromErr(err) - } - if err := d.Set("memory", params.Memory); err != nil { - return diag.FromErr(err) - } + // Save system id d.SetId(machine.SystemID) // Wait for VM host machine to be ready From 3e50b399d0838f8aaa70dabfd00ca67784f69c45 Mon Sep 17 00:00:00 2001 From: Ionut Balutoiu Date: Mon, 28 Jun 2021 19:19:09 +0300 Subject: [PATCH 02/14] Implement network managed resources The following network managed resources are added: * maas_fabric * maas_vlan * maas_subnet * maas_space --- examples/network.tf | 21 +++ go.mod | 2 +- go.sum | 6 +- maas/provider.go | 4 + maas/resource_maas_fabric.go | 129 ++++++++++++++++++ maas/resource_maas_space.go | 123 ++++++++++++++++++ maas/resource_maas_subnet.go | 245 +++++++++++++++++++++++++++++++++++ maas/resource_maas_vlan.go | 191 +++++++++++++++++++++++++++ 8 files changed, 718 insertions(+), 3 deletions(-) create mode 100644 maas/resource_maas_fabric.go create mode 100644 maas/resource_maas_space.go create mode 100644 maas/resource_maas_subnet.go create mode 100644 maas/resource_maas_vlan.go diff --git a/examples/network.tf b/examples/network.tf index 65cec5f8..19e732d1 100644 --- a/examples/network.tf +++ b/examples/network.tf @@ -21,3 +21,24 @@ data "maas_subnet" "vid10" { cidr = "10.10.0.0/16" vlan_id = data.maas_vlan.vid10.id } + +resource "maas_space" "tf_space" { + name = "tf-space" +} + +resource "maas_fabric" "tf_fabric" { + name = "tf-fabric" +} + +resource "maas_vlan" "tf_vlan" { + fabric = maas_fabric.tf_fabric.id + vid = 14 + name = "tf-vlan14" + space = maas_space.tf_space.name +} + +resource "maas_subnet" "tf_subnet" { + cidr = "10.88.88.0/24" + fabric = maas_fabric.tf_fabric.id + vlan = maas_vlan.tf_vlan.vid +} diff --git a/go.mod b/go.mod index 57132514..1a6f53e5 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,6 @@ go 1.16 require ( github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/terraform-plugin-sdk/v2 v2.6.1 - github.com/ionutbalutoiu/gomaasclient v0.0.0-20210622181848-f6c5d57f152e + github.com/ionutbalutoiu/gomaasclient v0.0.0-20210628164022-f9153fd6e77f github.com/stretchr/testify v1.7.0 ) diff --git a/go.sum b/go.sum index c7f64fb1..f4594176 100644 --- a/go.sum +++ b/go.sum @@ -136,6 +136,8 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0 h1:pMen7vLs8nvgEYhywH3KDWJIJTeEr2ULsVWHWYHQyBs= @@ -202,8 +204,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/ionutbalutoiu/gomaasclient v0.0.0-20210622181848-f6c5d57f152e h1:LrHHyulDteBT4sA01PxV9WJz7D/148qQRpfp3HbUoYA= -github.com/ionutbalutoiu/gomaasclient v0.0.0-20210622181848-f6c5d57f152e/go.mod h1:dec5eQdl7Oz+SuEinaMgVbA33wNMAbI9+uoKlfdIFdQ= +github.com/ionutbalutoiu/gomaasclient v0.0.0-20210628164022-f9153fd6e77f h1:XKsgG+zdtRb4iV8vsi1O8FSGTASa9u/OOtSrHm54eaY= +github.com/ionutbalutoiu/gomaasclient v0.0.0-20210628164022-f9153fd6e77f/go.mod h1:bj39184ChgZMBH01zYHoUV2h4mSmAYisQSHs/sRzYQE= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= diff --git a/maas/provider.go b/maas/provider.go index 8ba6ed5c..e2b7d130 100644 --- a/maas/provider.go +++ b/maas/provider.go @@ -38,6 +38,10 @@ func Provider() *schema.Provider { "maas_machine": resourceMaasMachine(), "maas_network_interface_physical": resourceMaasNetworkInterfacePhysical(), "maas_network_interface_link": resourceMaasNetworkInterfaceLink(), + "maas_fabric": resourceMaasFabric(), + "maas_vlan": resourceMaasVlan(), + "maas_subnet": resourceMaasSubnet(), + "maas_space": resourceMaasSpace(), "maas_block_device": resourceMaasBlockDevice(), "maas_tag": resourceMaasTag(), }, diff --git a/maas/resource_maas_fabric.go b/maas/resource_maas_fabric.go new file mode 100644 index 00000000..2add3103 --- /dev/null +++ b/maas/resource_maas_fabric.go @@ -0,0 +1,129 @@ +package maas + +import ( + "context" + "fmt" + "strconv" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/ionutbalutoiu/gomaasclient/client" + "github.com/ionutbalutoiu/gomaasclient/entity" +) + +func resourceMaasFabric() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceFabricCreate, + ReadContext: resourceFabricRead, + UpdateContext: resourceFabricUpdate, + DeleteContext: resourceFabricDelete, + Importer: &schema.ResourceImporter{ + StateContext: func(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + client := m.(*client.Client) + fabric, err := getFabric(client, d.Id()) + if err != nil { + return nil, err + } + if err := d.Set("name", fabric.Name); err != nil { + return nil, err + } + d.SetId(fmt.Sprintf("%v", fabric.ID)) + return []*schema.ResourceData{d}, nil + }, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +func resourceFabricCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + + fabric, err := client.Fabrics.Create(getFabricParams(d)) + if err != nil { + return diag.FromErr(err) + } + d.SetId(fmt.Sprintf("%v", fabric.ID)) + + return resourceFabricUpdate(ctx, d, m) +} + +func resourceFabricRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + + id, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + _, err = client.Fabric.Get(id) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceFabricUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + + id, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + _, err = client.Fabric.Update(id, getFabricParams(d)) + if err != nil { + return diag.FromErr(err) + } + + return resourceFabricRead(ctx, d, m) +} + +func resourceFabricDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + + id, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + err = client.Fabric.Delete(id) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func getFabricParams(d *schema.ResourceData) *entity.FabricParams { + return &entity.FabricParams{ + Name: d.Get("name").(string), + } +} + +func findFabric(client *client.Client, identifier string) (*entity.Fabric, error) { + fabrics, err := client.Fabrics.Get() + if err != nil { + return nil, err + } + for _, f := range fabrics { + if fmt.Sprintf("%v", f.ID) == identifier || f.Name == identifier { + return &f, nil + } + } + return nil, nil +} + +func getFabric(client *client.Client, identifier string) (*entity.Fabric, error) { + fabric, err := findFabric(client, identifier) + if err != nil { + return nil, err + } + if fabric == nil { + return nil, fmt.Errorf("fabric (%s) was not found", identifier) + } + return fabric, nil +} diff --git a/maas/resource_maas_space.go b/maas/resource_maas_space.go new file mode 100644 index 00000000..5477b31b --- /dev/null +++ b/maas/resource_maas_space.go @@ -0,0 +1,123 @@ +package maas + +import ( + "context" + "fmt" + "strconv" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/ionutbalutoiu/gomaasclient/client" + "github.com/ionutbalutoiu/gomaasclient/entity" +) + +func resourceMaasSpace() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceSpaceCreate, + ReadContext: resourceSpaceRead, + UpdateContext: resourceSpaceUpdate, + DeleteContext: resourceSpaceDelete, + Importer: &schema.ResourceImporter{ + StateContext: func(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + client := m.(*client.Client) + space, err := getSpace(client, d.Id()) + if err != nil { + return nil, err + } + if err := d.Set("name", space.Name); err != nil { + return nil, err + } + d.SetId(fmt.Sprintf("%v", space.ID)) + return []*schema.ResourceData{d}, nil + }, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +func resourceSpaceCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + + space, err := client.Spaces.Create(d.Get("name").(string)) + if err != nil { + return diag.FromErr(err) + } + d.SetId(fmt.Sprintf("%v", space.ID)) + + return resourceSpaceUpdate(ctx, d, m) +} + +func resourceSpaceRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + + id, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + _, err = client.Space.Get(id) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceSpaceUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + + id, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + _, err = client.Space.Update(id, d.Get("name").(string)) + if err != nil { + return diag.FromErr(err) + } + + return resourceSpaceRead(ctx, d, m) +} + +func resourceSpaceDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + + id, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + err = client.Space.Delete(id) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func findSpace(client *client.Client, identifier string) (*entity.Space, error) { + spaces, err := client.Spaces.Get() + if err != nil { + return nil, err + } + for _, s := range spaces { + if fmt.Sprintf("%v", s.ID) == identifier || s.Name == identifier { + return &s, nil + } + } + return nil, nil +} + +func getSpace(client *client.Client, identifier string) (*entity.Space, error) { + space, err := findSpace(client, identifier) + if err != nil { + return nil, err + } + if space == nil { + return nil, fmt.Errorf("space (%s) was not found", identifier) + } + return space, nil +} diff --git a/maas/resource_maas_subnet.go b/maas/resource_maas_subnet.go new file mode 100644 index 00000000..54ed1dbd --- /dev/null +++ b/maas/resource_maas_subnet.go @@ -0,0 +1,245 @@ +package maas + +import ( + "context" + "fmt" + "net" + "strconv" + + "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/ionutbalutoiu/gomaasclient/client" + "github.com/ionutbalutoiu/gomaasclient/entity" +) + +func resourceMaasSubnet() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceSubnetCreate, + ReadContext: resourceSubnetRead, + UpdateContext: resourceSubnetUpdate, + DeleteContext: resourceSubnetDelete, + Importer: &schema.ResourceImporter{ + StateContext: func(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + client := m.(*client.Client) + subnet, err := getSubnet(client, d.Id()) + if err != nil { + return nil, err + } + dnsServers := make([]string, len(subnet.DNSServers)) + for i, ip := range subnet.DNSServers { + dnsServers[i] = ip.String() + } + tfState := map[string]interface{}{ + "cidr": subnet.CIDR, + "name": subnet.Name, + "fabric": subnet.VLAN.FabricID, + "vlan": subnet.VLAN.VID, + "gateway_ip": subnet.GatewayIP, + "dns_servers": dnsServers, + "rdns_mode": subnet.RDNSMode, + "allow_dns": subnet.AllowDNS, + "allow_proxy": subnet.AllowProxy, + "managed": subnet.Managed, + } + for k, v := range tfState { + if err := d.Set(k, v); err != nil { + return nil, err + } + } + d.SetId(fmt.Sprintf("%v", subnet.ID)) + return []*schema.ResourceData{d}, nil + }, + }, + + Schema: map[string]*schema.Schema{ + "cidr": { + Type: schema.TypeString, + Required: true, + }, + "name": { + Type: schema.TypeString, + Optional: true, + }, + "fabric": { + Type: schema.TypeString, + Optional: true, + }, + "vlan": { + Type: schema.TypeString, + Optional: true, + RequiredWith: []string{"fabric"}, + }, + "gateway_ip": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: func(value interface{}, path cty.Path) diag.Diagnostics { + v := value.(string) + if ip := net.ParseIP(v); ip == nil { + return diag.FromErr(fmt.Errorf("gateway_ip must be a valid IP address (got '%s')", v)) + } + return nil + }, + }, + "dns_servers": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateDiagFunc: func(value interface{}, path cty.Path) diag.Diagnostics { + v := value.(string) + if ip := net.ParseIP(v); ip == nil { + return diag.FromErr(fmt.Errorf("dns_servers list must have valid IP addresses (found '%s' in the list)", v)) + } + return nil + }, + }, + }, + "rdns_mode": { + Type: schema.TypeInt, + Optional: true, + }, + "allow_dns": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "allow_proxy": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "managed": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + }, + } +} + +func resourceSubnetCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + + params, err := getSubnetParams(client, d) + if err != nil { + return diag.FromErr(err) + } + subnet, err := client.Subnets.Create(params) + if err != nil { + return diag.FromErr(err) + } + d.SetId(fmt.Sprintf("%v", subnet.ID)) + + return resourceSubnetUpdate(ctx, d, m) +} + +func resourceSubnetRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + + id, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + _, err = client.Subnet.Get(id) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceSubnetUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + + id, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + params, err := getSubnetParams(client, d) + if err != nil { + return diag.FromErr(err) + } + _, err = client.Subnet.Update(id, params) + if err != nil { + return diag.FromErr(err) + } + + return resourceSubnetRead(ctx, d, m) +} + +func resourceSubnetDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + + id, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + err = client.Subnet.Delete(id) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func getSubnetParams(client *client.Client, d *schema.ResourceData) (*entity.SubnetParams, error) { + params := entity.SubnetParams{ + CIDR: d.Get("cidr").(string), + AllowDNS: d.Get("allow_dns").(bool), + AllowProxy: d.Get("allow_proxy").(bool), + Managed: d.Get("managed").(bool), + } + if p, ok := d.GetOk("name"); ok { + params.Name = p.(string) + } + if p, ok := d.GetOk("fabric"); ok { + fabric, err := getFabric(client, p.(string)) + if err != nil { + return nil, err + } + params.Fabric = fmt.Sprintf("%v", fabric.ID) + if p, ok := d.GetOk("vlan"); ok { + vlan, err := getVlan(client, fabric.ID, p.(string)) + if err != nil { + return nil, err + } + params.VLAN = fmt.Sprintf("%v", vlan.ID) + params.VID = vlan.VID + } + } + if p, ok := d.GetOk("gateway_ip"); ok { + params.GatewayIP = net.ParseIP(p.(string)) + } + if p, ok := d.GetOk("dns_servers"); ok { + params.DNSServers = p.([]string) + } + if p, ok := d.GetOk("rdns_mode"); ok { + params.RDNSMode = p.(int) + } + return ¶ms, nil +} + +func findSubnet(client *client.Client, identifier string) (*entity.Subnet, error) { + subnets, err := client.Subnets.Get() + if err != nil { + return nil, err + } + for _, s := range subnets { + if fmt.Sprintf("%v", s.ID) == identifier || s.CIDR == identifier || s.Name == identifier { + return &s, nil + } + } + return nil, nil +} + +func getSubnet(client *client.Client, identifier string) (*entity.Subnet, error) { + subnet, err := findSubnet(client, identifier) + if err != nil { + return nil, err + } + if subnet == nil { + return nil, fmt.Errorf("subnet (%s) was not found", identifier) + } + return subnet, nil +} diff --git a/maas/resource_maas_vlan.go b/maas/resource_maas_vlan.go new file mode 100644 index 00000000..f59846f0 --- /dev/null +++ b/maas/resource_maas_vlan.go @@ -0,0 +1,191 @@ +package maas + +import ( + "context" + "fmt" + "strconv" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/ionutbalutoiu/gomaasclient/client" + "github.com/ionutbalutoiu/gomaasclient/entity" +) + +func resourceMaasVlan() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceVlanCreate, + ReadContext: resourceVlanRead, + UpdateContext: resourceVlanUpdate, + DeleteContext: resourceVlanDelete, + Importer: &schema.ResourceImporter{ + StateContext: func(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + idParts := strings.Split(d.Id(), ":") + if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { + return nil, fmt.Errorf("unexpected format of ID (%q), expected FABRIC:VLAN", d.Id()) + } + client := m.(*client.Client) + fabric, err := getFabric(client, idParts[0]) + if err != nil { + return nil, err + } + vlan, err := getVlan(client, fabric.ID, idParts[1]) + if err != nil { + return nil, err + } + if err := d.Set("fabric", fmt.Sprintf("%v", fabric.ID)); err != nil { + return nil, err + } + if err := d.Set("vid", vlan.VID); err != nil { + return nil, err + } + if err := d.Set("name", vlan.Name); err != nil { + return nil, err + } + if err := d.Set("mtu", vlan.MTU); err != nil { + return nil, err + } + if err := d.Set("space", vlan.Space); err != nil { + return nil, err + } + d.SetId(fmt.Sprintf("%v", vlan.VID)) + return []*schema.ResourceData{d}, nil + }, + }, + + Schema: map[string]*schema.Schema{ + "fabric": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "vid": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + "mtu": { + Type: schema.TypeInt, + Optional: true, + Default: 1500, + }, + "name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "space": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + } +} + +func resourceVlanCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + + fabric, err := getFabric(client, d.Get("fabric").(string)) + if err != nil { + return diag.FromErr(err) + } + vlan, err := client.VLANs.Create(fabric.ID, getVlanParams(d)) + if err != nil { + return diag.FromErr(err) + } + d.SetId(fmt.Sprintf("%v", vlan.VID)) + + return resourceVlanUpdate(ctx, d, m) +} + +func resourceVlanRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + + fabric, err := getFabric(client, d.Get("fabric").(string)) + if err != nil { + return diag.FromErr(err) + } + _, err = getVlan(client, fabric.ID, d.Id()) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceVlanUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + + id, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + fabric, err := getFabric(client, d.Get("fabric").(string)) + if err != nil { + return diag.FromErr(err) + } + _, err = client.VLAN.Update(fabric.ID, id, getVlanParams(d)) + if err != nil { + return diag.FromErr(err) + } + + return resourceVlanRead(ctx, d, m) +} + +func resourceVlanDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + + id, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + fabric, err := getFabric(client, d.Get("fabric").(string)) + if err != nil { + return diag.FromErr(err) + } + err = client.VLAN.Delete(fabric.ID, id) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func getVlanParams(d *schema.ResourceData) *entity.VLANParams { + params := entity.VLANParams{ + VID: d.Get("vid").(int), + MTU: d.Get("mtu").(int), + } + if p, ok := d.GetOk("name"); ok { + params.Name = p.(string) + } + if p, ok := d.GetOk("space"); ok { + params.Space = p.(string) + } + return ¶ms +} + +func findVlan(client *client.Client, fabricID int, identifier string) (*entity.VLAN, error) { + vlans, err := client.VLANs.Get(fabricID) + if err != nil { + return nil, err + } + for _, v := range vlans { + if fmt.Sprintf("%v", v.VID) == identifier || fmt.Sprintf("%v", v.ID) == identifier || fmt.Sprintf("%v", v.Name) == identifier { + return &v, nil + } + } + return nil, nil +} + +func getVlan(client *client.Client, fabricID int, identifier string) (*entity.VLAN, error) { + vlan, err := findVlan(client, fabricID, identifier) + if err != nil { + return nil, err + } + if vlan == nil { + return nil, fmt.Errorf("vlan (%s) was not found", identifier) + } + return vlan, nil +} From 485151e840f60e4ef1547f01382a2f7256430f81 Mon Sep 17 00:00:00 2001 From: Ionut Balutoiu Date: Tue, 29 Jun 2021 17:49:45 +0300 Subject: [PATCH 03/14] Implement ip_ranges config to maas_subnet resource --- examples/network.tf | 15 +++++ go.mod | 2 +- go.sum | 4 +- maas/resource_maas_subnet.go | 122 +++++++++++++++++++++++++++++------ 4 files changed, 119 insertions(+), 24 deletions(-) diff --git a/examples/network.tf b/examples/network.tf index 19e732d1..bb218172 100644 --- a/examples/network.tf +++ b/examples/network.tf @@ -41,4 +41,19 @@ resource "maas_subnet" "tf_subnet" { cidr = "10.88.88.0/24" fabric = maas_fabric.tf_fabric.id vlan = maas_vlan.tf_vlan.vid + name = "tf_subnet" + gateway_ip = "10.88.88.1" + dns_servers = [ + "1.1.1.1", + ] + ip_ranges { + type = "reserved" + start_ip = "10.88.88.1" + end_ip = "10.88.88.50" + } + ip_ranges { + type = "dynamic" + start_ip = "10.88.88.200" + end_ip = "10.88.88.254" + } } diff --git a/go.mod b/go.mod index 1a6f53e5..36c3140b 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,6 @@ go 1.16 require ( github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/terraform-plugin-sdk/v2 v2.6.1 - github.com/ionutbalutoiu/gomaasclient v0.0.0-20210628164022-f9153fd6e77f + github.com/ionutbalutoiu/gomaasclient v0.0.0-20210629144238-745a2ee458bd github.com/stretchr/testify v1.7.0 ) diff --git a/go.sum b/go.sum index f4594176..2f92306b 100644 --- a/go.sum +++ b/go.sum @@ -204,8 +204,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/ionutbalutoiu/gomaasclient v0.0.0-20210628164022-f9153fd6e77f h1:XKsgG+zdtRb4iV8vsi1O8FSGTASa9u/OOtSrHm54eaY= -github.com/ionutbalutoiu/gomaasclient v0.0.0-20210628164022-f9153fd6e77f/go.mod h1:bj39184ChgZMBH01zYHoUV2h4mSmAYisQSHs/sRzYQE= +github.com/ionutbalutoiu/gomaasclient v0.0.0-20210629144238-745a2ee458bd h1:4QUhuJTuh3wENOU3kAFpme4dQ8gN5b/dtlPGXTfYqMs= +github.com/ionutbalutoiu/gomaasclient v0.0.0-20210629144238-745a2ee458bd/go.mod h1:bj39184ChgZMBH01zYHoUV2h4mSmAYisQSHs/sRzYQE= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= diff --git a/maas/resource_maas_subnet.go b/maas/resource_maas_subnet.go index 54ed1dbd..3b5d0da1 100644 --- a/maas/resource_maas_subnet.go +++ b/maas/resource_maas_subnet.go @@ -3,12 +3,13 @@ package maas import ( "context" "fmt" - "net" "strconv" "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/go-cty/cty/gocty" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/ionutbalutoiu/gomaasclient/client" "github.com/ionutbalutoiu/gomaasclient/entity" ) @@ -33,9 +34,9 @@ func resourceMaasSubnet() *schema.Resource { tfState := map[string]interface{}{ "cidr": subnet.CIDR, "name": subnet.Name, - "fabric": subnet.VLAN.FabricID, - "vlan": subnet.VLAN.VID, - "gateway_ip": subnet.GatewayIP, + "fabric": fmt.Sprintf("%v", subnet.VLAN.FabricID), + "vlan": fmt.Sprintf("%v", subnet.VLAN.VID), + "gateway_ip": subnet.GatewayIP.String(), "dns_servers": dnsServers, "rdns_mode": subnet.RDNSMode, "allow_dns": subnet.AllowDNS, @@ -71,27 +72,67 @@ func resourceMaasSubnet() *schema.Resource { RequiredWith: []string{"fabric"}, }, "gateway_ip": { - Type: schema.TypeString, - Optional: true, - ValidateDiagFunc: func(value interface{}, path cty.Path) diag.Diagnostics { - v := value.(string) - if ip := net.ParseIP(v); ip == nil { - return diag.FromErr(fmt.Errorf("gateway_ip must be a valid IP address (got '%s')", v)) - } - return nil - }, + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.IsIPAddress), }, "dns_servers": { Type: schema.TypeList, Optional: true, Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateDiagFunc: func(value interface{}, path cty.Path) diag.Diagnostics { - v := value.(string) - if ip := net.ParseIP(v); ip == nil { - return diag.FromErr(fmt.Errorf("dns_servers list must have valid IP addresses (found '%s' in the list)", v)) + ValidateDiagFunc: func(i interface{}, p cty.Path) diag.Diagnostics { + var diags diag.Diagnostics + + attr := p[len(p)-1].(cty.IndexStep) + var index int + if err := gocty.FromCtyValue(attr.Key, &index); err != nil { + return diag.FromErr(err) + } + ws, es := validation.IsIPAddress(i, fmt.Sprintf("dns_servers[%v]", index)) + + for _, w := range ws { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Warning, + Summary: w, + AttributePath: p, + }) } - return nil + for _, e := range es { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: e.Error(), + AttributePath: p, + }) + } + return diags + }, + Type: schema.TypeString, + }, + }, + "ip_ranges": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"dynamic", "reserved"}, false)), + }, + "start_ip": { + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.IsIPAddress), + }, + "end_ip": { + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.IsIPAddress), + }, + "comment": { + Type: schema.TypeString, + Optional: true, + }, }, }, }, @@ -164,6 +205,9 @@ func resourceSubnetUpdate(ctx context.Context, d *schema.ResourceData, m interfa if err != nil { return diag.FromErr(err) } + if err := updateIPRanges(client, d, id); err != nil { + return diag.FromErr(err) + } return resourceSubnetRead(ctx, d, m) } @@ -183,6 +227,42 @@ func resourceSubnetDelete(ctx context.Context, d *schema.ResourceData, m interfa return nil } +func updateIPRanges(client *client.Client, d *schema.ResourceData, subnetID int) error { + p, ok := d.GetOk("ip_ranges") + if !ok { + return nil + } + // Removing existing IP ranges on this subnet + ipRanges, err := client.IPRanges.Get() + if err != nil { + return err + } + for _, ipr := range ipRanges { + if ipr.Subnet.ID != subnetID { + continue + } + if err := client.IPRange.Delete(ipr.ID); err != nil { + return err + } + } + // Create the new IP ranges on this subnet + for _, i := range p.(*schema.Set).List() { + ipr := i.(map[string]interface{}) + params := entity.IPRangeParams{ + Subnet: fmt.Sprintf("%v", subnetID), + Type: ipr["type"].(string), + StartIP: ipr["start_ip"].(string), + EndIP: ipr["end_ip"].(string), + Comment: ipr["comment"].(string), + } + _, err := client.IPRanges.Create(¶ms) + if err != nil { + return err + } + } + return nil +} + func getSubnetParams(client *client.Client, d *schema.ResourceData) (*entity.SubnetParams, error) { params := entity.SubnetParams{ CIDR: d.Get("cidr").(string), @@ -209,10 +289,10 @@ func getSubnetParams(client *client.Client, d *schema.ResourceData) (*entity.Sub } } if p, ok := d.GetOk("gateway_ip"); ok { - params.GatewayIP = net.ParseIP(p.(string)) + params.GatewayIP = p.(string) } if p, ok := d.GetOk("dns_servers"); ok { - params.DNSServers = p.([]string) + params.DNSServers = convertToStringSlice(p) } if p, ok := d.GetOk("rdns_mode"); ok { params.RDNSMode = p.(int) From 58a9b0651e7849ec7d7ad947e6e3427f3aad1cbf Mon Sep 17 00:00:00 2001 From: Ionut Balutoiu Date: Wed, 30 Jun 2021 14:09:12 +0300 Subject: [PATCH 04/14] Implement maas_subnet_ip_range managed resource --- maas/provider.go | 1 + maas/resource_maas_subnet_ip_range.go | 175 ++++++++++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 maas/resource_maas_subnet_ip_range.go diff --git a/maas/provider.go b/maas/provider.go index e2b7d130..b21964ac 100644 --- a/maas/provider.go +++ b/maas/provider.go @@ -41,6 +41,7 @@ func Provider() *schema.Provider { "maas_fabric": resourceMaasFabric(), "maas_vlan": resourceMaasVlan(), "maas_subnet": resourceMaasSubnet(), + "maas_subnet_ip_range": resourceMaasSubnetIPRange(), "maas_space": resourceMaasSpace(), "maas_block_device": resourceMaasBlockDevice(), "maas_tag": resourceMaasTag(), diff --git a/maas/resource_maas_subnet_ip_range.go b/maas/resource_maas_subnet_ip_range.go new file mode 100644 index 00000000..025510a5 --- /dev/null +++ b/maas/resource_maas_subnet_ip_range.go @@ -0,0 +1,175 @@ +package maas + +import ( + "context" + "fmt" + "strconv" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/ionutbalutoiu/gomaasclient/client" + "github.com/ionutbalutoiu/gomaasclient/entity" +) + +func resourceMaasSubnetIPRange() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceSubnetIPRangeCreate, + ReadContext: resourceSubnetIPRangeRead, + UpdateContext: resourceSubnetIPRangeUpdate, + DeleteContext: resourceSubnetIPRangeDelete, + Importer: &schema.ResourceImporter{ + StateContext: func(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + client := m.(*client.Client) + idParts := strings.Split(d.Id(), ":") + var ipRange *entity.IPRange + var err error + if len(idParts) == 2 { + if idParts[0] == "" || idParts[1] == "" { + return nil, fmt.Errorf("unexpected format of ID (%q), expected START_IP:END_IP", d.Id()) + } + ipRange, err = getSubnetIPRange(client, idParts[0], idParts[1]) + if err != nil { + return nil, err + } + } else { + id, err := strconv.Atoi(d.Id()) + if err != nil { + return nil, err + } + ipRange, err = client.IPRange.Get(id) + if err != nil { + return nil, err + } + } + tfState := map[string]interface{}{ + "subnet": ipRange.Subnet.ID, + "type": ipRange.Type, + "start_ip": ipRange.StartIP.String(), + "end_ip": ipRange.EndIP.String(), + "comment": ipRange.Comment, + } + for k, v := range tfState { + if err := d.Set(k, v); err != nil { + return nil, err + } + } + d.SetId(fmt.Sprintf("%v", ipRange.ID)) + return []*schema.ResourceData{d}, nil + }, + }, + + Schema: map[string]*schema.Schema{ + "subnet": { + Type: schema.TypeString, + Required: true, + }, + "type": { + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"dynamic", "reserved"}, false)), + }, + "start_ip": { + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.IsIPAddress), + }, + "end_ip": { + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.IsIPAddress), + }, + "comment": { + Type: schema.TypeString, + Optional: true, + }, + }, + } +} + +func resourceSubnetIPRangeCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + + subnet, err := findSubnet(client, d.Get("subnet").(string)) + if err != nil { + return diag.FromErr(err) + } + ipRange, err := client.IPRanges.Create(getSubnetIPRangeParams(d, subnet.ID)) + if err != nil { + return diag.FromErr(err) + } + d.SetId(fmt.Sprintf("%v", ipRange.ID)) + + return resourceSubnetIPRangeUpdate(ctx, d, m) +} + +func resourceSubnetIPRangeRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + + id, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + _, err = client.IPRange.Get(id) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceSubnetIPRangeUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + + id, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + subnet, err := findSubnet(client, d.Get("subnet").(string)) + if err != nil { + return diag.FromErr(err) + } + if _, err := client.IPRange.Update(id, getSubnetIPRangeParams(d, subnet.ID)); err != nil { + return diag.FromErr(err) + } + + return resourceSubnetIPRangeRead(ctx, d, m) +} + +func resourceSubnetIPRangeDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + + id, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + if err := client.IPRange.Delete(id); err != nil { + return diag.FromErr(err) + } + + return nil +} + +func getSubnetIPRangeParams(d *schema.ResourceData, subnetID int) *entity.IPRangeParams { + return &entity.IPRangeParams{ + Subnet: fmt.Sprintf("%v", subnetID), + Type: d.Get("type").(string), + StartIP: d.Get("start_ip").(string), + EndIP: d.Get("end_ip").(string), + Comment: d.Get("comment").(string), + } +} + +func getSubnetIPRange(client *client.Client, startIP string, endIP string) (*entity.IPRange, error) { + ipRanges, err := client.IPRanges.Get() + if err != nil { + return nil, err + } + for _, ipr := range ipRanges { + if ipr.StartIP.String() == startIP && ipr.EndIP.String() == endIP { + return &ipr, nil + } + } + return nil, fmt.Errorf("IP range (%s->%s) was not found", startIP, endIP) +} From 3392031f3428551e5531dcd86cfdacfe37dd124e Mon Sep 17 00:00:00 2001 From: Ionut Balutoiu Date: Wed, 30 Jun 2021 14:09:39 +0300 Subject: [PATCH 05/14] Re-organize examples Also: * Add `maas_subnet_ip_range` managed resources examples * Add two more managed machines as examples --- examples/{network.tf => 0-network.tf} | 25 ++++++++++++ examples/{machines.tf => 1-machines.tf} | 46 +++++++++++++++++++++++ examples/{vm_hosts.tf => 2-vm_hosts.tf} | 2 +- examples/{tags.tf => 3-tags.tf} | 0 examples/{instances.tf => 4-instances.tf} | 4 +- examples/output.tf | 35 ++++++++++------- 6 files changed, 95 insertions(+), 17 deletions(-) rename examples/{network.tf => 0-network.tf} (65%) rename examples/{machines.tf => 1-machines.tf} (79%) rename examples/{vm_hosts.tf => 2-vm_hosts.tf} (95%) rename examples/{tags.tf => 3-tags.tf} (100%) rename examples/{instances.tf => 4-instances.tf} (88%) diff --git a/examples/network.tf b/examples/0-network.tf similarity index 65% rename from examples/network.tf rename to examples/0-network.tf index bb218172..5a6b7a85 100644 --- a/examples/network.tf +++ b/examples/0-network.tf @@ -57,3 +57,28 @@ resource "maas_subnet" "tf_subnet" { end_ip = "10.88.88.254" } } + +resource "maas_subnet" "tf_subnet_2" { + cidr = "10.77.77.0/24" + name = "tf_subnet_2" + fabric = maas_fabric.tf_fabric.id + gateway_ip = "10.77.77.1" + dns_servers = [ + "1.1.1.1", + ] +} + +resource "maas_subnet_ip_range" "dynamic_ip_range" { + subnet = maas_subnet.tf_subnet_2.id + type = "dynamic" + start_ip = "10.77.77.2" + end_ip = "10.77.77.60" +} + +resource "maas_subnet_ip_range" "reserved_ip_range" { + subnet = maas_subnet.tf_subnet_2.id + type = "reserved" + start_ip = "10.77.77.200" + end_ip = "10.77.77.254" + comment = "Reserved for Static IPs" +} diff --git a/examples/machines.tf b/examples/1-machines.tf similarity index 79% rename from examples/machines.tf rename to examples/1-machines.tf index cad2f93b..1a5e8130 100644 --- a/examples/machines.tf +++ b/examples/1-machines.tf @@ -180,3 +180,49 @@ resource "maas_block_device" "vdc" { mount_point = "/images" } } + +# +# Machine 3 +# +resource "maas_machine" "virsh_vm3" { + power_type = "virsh" + power_parameters = { + power_address = "qemu+ssh://ubuntu@10.113.1.21/system" + power_id = "machine-01" + } + pxe_mac_address = "52:54:00:16:78:ec" +} + +resource "maas_network_interface_physical" "virsh_vm3_nic1" { + machine_id = maas_machine.virsh_vm3.id + mac_address = "52:54:00:16:78:ec" + name = "eth0" + vlan = data.maas_vlan.default.id +} + +resource "maas_network_interface_physical" "virsh_vm3_nic2" { + machine_id = maas_machine.virsh_vm3.id + mac_address = "52:54:00:37:19:da" + name = "eth1" + vlan = data.maas_vlan.vid10.id +} + +resource "maas_network_interface_link" "virsh_vm3_nic1" { + machine_id = maas_machine.virsh_vm3.id + network_interface_id = maas_network_interface_physical.virsh_vm3_nic1.id + subnet_id = data.maas_subnet.pxe.id + mode = "AUTO" + default_gateway = true +} + +# +# Machine 4 +# +resource "maas_machine" "virsh_vm4" { + power_type = "virsh" + power_parameters = { + power_address = "qemu+ssh://ubuntu@10.113.1.22/system" + power_id = "machine-05" + } + pxe_mac_address = "52:54:00:c4:74:96" +} diff --git a/examples/vm_hosts.tf b/examples/2-vm_hosts.tf similarity index 95% rename from examples/vm_hosts.tf rename to examples/2-vm_hosts.tf index 6586370a..d0b90401 100644 --- a/examples/vm_hosts.tf +++ b/examples/2-vm_hosts.tf @@ -21,7 +21,7 @@ resource "maas_vm_host_machine" "kvm" { resource "maas_vm_host" "maas_machine" { type = "virsh" - machine = "machine-01" + machine = maas_machine.virsh_vm3.hostname } resource "maas_vm_host_machine" "maas_machine_1" { diff --git a/examples/tags.tf b/examples/3-tags.tf similarity index 100% rename from examples/tags.tf rename to examples/3-tags.tf diff --git a/examples/instances.tf b/examples/4-instances.tf similarity index 88% rename from examples/instances.tf rename to examples/4-instances.tf index 48d50bd3..557fd8d9 100644 --- a/examples/instances.tf +++ b/examples/4-instances.tf @@ -15,9 +15,9 @@ resource "maas_instance" "kvm" { } } -resource "maas_instance" "machine_05" { +resource "maas_instance" "virsh_vm4" { allocate_params { - hostname = "machine-05" + hostname = maas_machine.virsh_vm4.hostname } deploy_params { distro_series = "focal" diff --git a/examples/output.tf b/examples/output.tf index 4c79428b..4ad022dd 100644 --- a/examples/output.tf +++ b/examples/output.tf @@ -1,21 +1,28 @@ # Network -output "maas_fabric" { value = data.maas_fabric.default } -output "maas_vlan_untagged" { value = data.maas_vlan.default } -output "maas_vlan_10" { value = data.maas_vlan.vid10 } -output "maas_subnet_pxe" { value = data.maas_subnet.pxe } -output "maas_subnet_vlan_10" { value = data.maas_subnet.vid10 } +output "maas_fabric" { value = data.maas_fabric.default } +output "maas_vlan_untagged" { value = data.maas_vlan.default } +output "maas_vlan_10" { value = data.maas_vlan.vid10 } +output "maas_subnet_pxe" { value = data.maas_subnet.pxe } +output "maas_subnet_vlan_10" { value = data.maas_subnet.vid10 } +output "maas_space_tf_space" { value = maas_space.tf_space } +output "maas_fabric_tf_fabric" { value = maas_fabric.tf_fabric } +output "maas_vlan_tf_vlan" { value = maas_vlan.tf_vlan } +output "maas_subnet_tf_subnet" { value = maas_subnet.tf_subnet } +output "maas_subnet_tf_subnet_2" { value = maas_subnet.tf_subnet_2 } +output "maas_subnet_ip_range_dynamic" { value = maas_subnet_ip_range.dynamic_ip_range } +output "maas_subnet_ip_range_reserved" { value = maas_subnet_ip_range.reserved_ip_range } # Machines output "maas_machine_1" { value = maas_machine.virsh_vm1.hostname } output "maas_machine_2" { value = maas_machine.virsh_vm2.hostname } # VM Hosts -output "maas_vm_host_kvm" { value = maas_vm_host.kvm.name } -output "maas_vm_host_kvm_1" { value = maas_vm_host_machine.kvm[0] } -output "maas_vm_host_kvm_2" { value = maas_vm_host_machine.kvm[1] } -output "maas_vm_host_maas_machine" { value = maas_vm_host.maas_machine.name } -output "maas_vm_host_maas_machine_1" { value = maas_vm_host_machine.maas_machine_1 } -output "maas_vm_host_maas_machine_2" { value = maas_vm_host_machine.maas_machine_2 } +output "maas_vm_host_kvm" { value = maas_vm_host.kvm.name } +output "maas_vm_host_kvm_1" { value = maas_vm_host_machine.kvm[0] } +output "maas_vm_host_kvm_2" { value = maas_vm_host_machine.kvm[1] } +output "maas_vm_host_maas_machine" { value = maas_vm_host.maas_machine.name } +output "maas_vm_host_maas_machine_1" { value = maas_vm_host_machine.maas_machine_1 } +output "maas_vm_host_maas_machine_2" { value = maas_vm_host_machine.maas_machine_2 } # Tags output "maas_tag_kvm" { value = maas_tag.kvm } @@ -23,6 +30,6 @@ output "maas_tag_virtual" { value = maas_tag.virtual } output "maas_tag_ubuntu" { value = maas_tag.ubuntu } # Instances -output "maas_instance_1" { value = maas_instance.kvm[0] } -output "maas_instance_2" { value = maas_instance.kvm[1] } -output "maas_instance_machine_05" { value = maas_instance.machine_05 } +output "maas_instance_1" { value = maas_instance.kvm[0] } +output "maas_instance_2" { value = maas_instance.kvm[1] } +output "maas_instance_virsh_vm4" { value = maas_instance.virsh_vm4 } From 69374cc53cd1ee9e91540c53b15b5e07f82f6a8b Mon Sep 17 00:00:00 2001 From: Ionut Balutoiu Date: Thu, 1 Jul 2021 19:37:50 +0300 Subject: [PATCH 06/14] General code cleanup --- examples/0-network.tf | 52 +++-- examples/1-machines.tf | 110 +++++------ examples/2-vm_hosts.tf | 10 +- examples/3-tags.tf | 6 +- go.mod | 2 +- go.sum | 4 +- maas/data_source_maas_fabric.go | 14 +- maas/data_source_maas_subnet.go | 100 ++++------ maas/data_source_maas_vlan.go | 50 +++-- maas/resource_maas_block_device.go | 125 +++++------- maas/resource_maas_fabric.go | 9 +- maas/resource_maas_instance.go | 133 ++++--------- maas/resource_maas_machine.go | 115 +++-------- maas/resource_maas_network_interface_link.go | 155 ++++++++------- ...esource_maas_network_interface_physical.go | 135 ++++++------- maas/resource_maas_space.go | 16 +- maas/resource_maas_subnet.go | 119 +++++------ maas/resource_maas_subnet_ip_range.go | 20 +- maas/resource_maas_tag.go | 75 +++++-- maas/resource_maas_vlan.go | 71 ++++--- maas/resource_maas_vm_host.go | 186 ++++++------------ maas/resource_maas_vm_host_machine.go | 119 +++-------- maas/utils.go | 62 ++++++ 23 files changed, 722 insertions(+), 966 deletions(-) diff --git a/examples/0-network.tf b/examples/0-network.tf index 5a6b7a85..231c0f9e 100644 --- a/examples/0-network.tf +++ b/examples/0-network.tf @@ -1,40 +1,50 @@ +# +# Spaces +# +resource "maas_space" "tf_space" { + name = "tf-space" +} + +# +# Fabrics +# data "maas_fabric" "default" { name = "maas" } +resource "maas_fabric" "tf_fabric" { + name = "tf-fabric" +} + +# +# VLANs +# data "maas_vlan" "default" { - fabric_id = data.maas_fabric.default.id - vid = 0 + fabric = data.maas_fabric.default.id + vlan = 0 } data "maas_vlan" "vid10" { - fabric_id = data.maas_fabric.default.id - vid = 10 + fabric = data.maas_fabric.default.id + vlan = 10 +} + +resource "maas_vlan" "tf_vlan" { + fabric = maas_fabric.tf_fabric.id + vid = 14 + name = "tf-vlan14" + space = maas_space.tf_space.name } +# +# Subnets +# data "maas_subnet" "pxe" { cidr = "10.99.0.0/16" - vlan_id = data.maas_vlan.default.id } data "maas_subnet" "vid10" { cidr = "10.10.0.0/16" - vlan_id = data.maas_vlan.vid10.id -} - -resource "maas_space" "tf_space" { - name = "tf-space" -} - -resource "maas_fabric" "tf_fabric" { - name = "tf-fabric" -} - -resource "maas_vlan" "tf_vlan" { - fabric = maas_fabric.tf_fabric.id - vid = 14 - name = "tf-vlan14" - space = maas_space.tf_space.name } resource "maas_subnet" "tf_subnet" { diff --git a/examples/1-machines.tf b/examples/1-machines.tf index 1a5e8130..3e2a2f48 100644 --- a/examples/1-machines.tf +++ b/examples/1-machines.tf @@ -11,7 +11,7 @@ resource "maas_machine" "virsh_vm1" { } resource "maas_network_interface_physical" "virsh_vm1_nic1" { - machine_id = maas_machine.virsh_vm1.id + machine = maas_machine.virsh_vm1.id mac_address = "52:54:00:89:f5:3e" name = "eth0" vlan = data.maas_vlan.default.id @@ -22,8 +22,17 @@ resource "maas_network_interface_physical" "virsh_vm1_nic1" { ] } +resource "maas_network_interface_link" "virsh_vm1_nic1" { + machine = maas_machine.virsh_vm1.id + network_interface = maas_network_interface_physical.virsh_vm1_nic1.id + subnet = data.maas_subnet.pxe.id + mode = "STATIC" + ip_address = "10.99.4.111" + default_gateway = true +} + resource "maas_network_interface_physical" "virsh_vm1_nic2" { - machine_id = maas_machine.virsh_vm1.id + machine = maas_machine.virsh_vm1.id mac_address = "52:54:00:f5:89:ae" name = "eth1" vlan = data.maas_vlan.vid10.id @@ -34,8 +43,15 @@ resource "maas_network_interface_physical" "virsh_vm1_nic2" { ] } +resource "maas_network_interface_link" "virsh_vm1_nic2" { + machine = maas_machine.virsh_vm1.id + network_interface = maas_network_interface_physical.virsh_vm1_nic2.id + subnet = data.maas_subnet.vid10.id + mode = "AUTO" +} + resource "maas_network_interface_physical" "virsh_vm1_nic3" { - machine_id = maas_machine.virsh_vm1.id + machine = maas_machine.virsh_vm1.id mac_address = "52:54:00:0e:92:79" name = "eth2" vlan = data.maas_vlan.default.id @@ -46,26 +62,10 @@ resource "maas_network_interface_physical" "virsh_vm1_nic3" { ] } -resource "maas_network_interface_link" "virsh_vm1_nic1" { - machine_id = maas_machine.virsh_vm1.id - network_interface_id = maas_network_interface_physical.virsh_vm1_nic1.id - subnet_id = data.maas_subnet.pxe.id - mode = "STATIC" - ip_address = "10.99.4.111" - default_gateway = true -} - -resource "maas_network_interface_link" "virsh_vm1_nic2" { - machine_id = maas_machine.virsh_vm1.id - network_interface_id = maas_network_interface_physical.virsh_vm1_nic2.id - subnet_id = data.maas_subnet.vid10.id - mode = "AUTO" -} - resource "maas_network_interface_link" "virsh_vm1_nic3" { - machine_id = maas_machine.virsh_vm1.id - network_interface_id = maas_network_interface_physical.virsh_vm1_nic3.id - subnet_id = data.maas_subnet.pxe.id + machine = maas_machine.virsh_vm1.id + network_interface = maas_network_interface_physical.virsh_vm1_nic3.id + subnet = data.maas_subnet.pxe.id mode = "DHCP" } @@ -82,7 +82,7 @@ resource "maas_machine" "virsh_vm2" { } resource "maas_network_interface_physical" "virsh_vm2_nic1" { - machine_id = maas_machine.virsh_vm2.id + machine = maas_machine.virsh_vm2.id mac_address = "52:54:00:7c:f7:77" name = "eno0" vlan = data.maas_vlan.default.id @@ -93,8 +93,17 @@ resource "maas_network_interface_physical" "virsh_vm2_nic1" { ] } +resource "maas_network_interface_link" "virsh_vm2_nic1" { + machine = maas_machine.virsh_vm2.id + network_interface = maas_network_interface_physical.virsh_vm2_nic1.id + subnet = data.maas_subnet.pxe.id + mode = "STATIC" + ip_address = "10.99.4.112" + default_gateway = true +} + resource "maas_network_interface_physical" "virsh_vm2_nic2" { - machine_id = maas_machine.virsh_vm2.id + machine = maas_machine.virsh_vm2.id mac_address = "52:54:00:82:5c:c1" name = "eno1" vlan = data.maas_vlan.default.id @@ -105,8 +114,15 @@ resource "maas_network_interface_physical" "virsh_vm2_nic2" { ] } +resource "maas_network_interface_link" "virsh_vm2_nic2" { + machine = maas_machine.virsh_vm2.id + network_interface = maas_network_interface_physical.virsh_vm2_nic2.id + subnet = data.maas_subnet.pxe.id + mode = "DHCP" +} + resource "maas_network_interface_physical" "virsh_vm2_nic3" { - machine_id = maas_machine.virsh_vm2.id + machine = maas_machine.virsh_vm2.id mac_address = "52:54:00:bb:6e:9f" name = "eno2" vlan = data.maas_vlan.vid10.id @@ -117,26 +133,10 @@ resource "maas_network_interface_physical" "virsh_vm2_nic3" { ] } -resource "maas_network_interface_link" "virsh_vm2_nic1" { - machine_id = maas_machine.virsh_vm2.id - network_interface_id = maas_network_interface_physical.virsh_vm2_nic1.id - subnet_id = data.maas_subnet.pxe.id - mode = "STATIC" - ip_address = "10.99.4.112" - default_gateway = true -} - -resource "maas_network_interface_link" "virsh_vm2_nic2" { - machine_id = maas_machine.virsh_vm2.id - network_interface_id = maas_network_interface_physical.virsh_vm2_nic2.id - subnet_id = data.maas_subnet.vid10.id - mode = "DHCP" -} - resource "maas_network_interface_link" "virsh_vm2_nic3" { - machine_id = maas_machine.virsh_vm2.id - network_interface_id = maas_network_interface_physical.virsh_vm2_nic3.id - subnet_id = data.maas_subnet.pxe.id + machine = maas_machine.virsh_vm2.id + network_interface = maas_network_interface_physical.virsh_vm2_nic3.id + subnet = data.maas_subnet.vid10.id mode = "AUTO" } @@ -193,28 +193,6 @@ resource "maas_machine" "virsh_vm3" { pxe_mac_address = "52:54:00:16:78:ec" } -resource "maas_network_interface_physical" "virsh_vm3_nic1" { - machine_id = maas_machine.virsh_vm3.id - mac_address = "52:54:00:16:78:ec" - name = "eth0" - vlan = data.maas_vlan.default.id -} - -resource "maas_network_interface_physical" "virsh_vm3_nic2" { - machine_id = maas_machine.virsh_vm3.id - mac_address = "52:54:00:37:19:da" - name = "eth1" - vlan = data.maas_vlan.vid10.id -} - -resource "maas_network_interface_link" "virsh_vm3_nic1" { - machine_id = maas_machine.virsh_vm3.id - network_interface_id = maas_network_interface_physical.virsh_vm3_nic1.id - subnet_id = data.maas_subnet.pxe.id - mode = "AUTO" - default_gateway = true -} - # # Machine 4 # diff --git a/examples/2-vm_hosts.tf b/examples/2-vm_hosts.tf index d0b90401..3cc04e81 100644 --- a/examples/2-vm_hosts.tf +++ b/examples/2-vm_hosts.tf @@ -1,3 +1,6 @@ +# +# VM Host 1 +# resource "maas_vm_host" "kvm" { type = "virsh" power_address = "qemu+ssh://ubuntu@10.113.1.24/system" @@ -19,6 +22,9 @@ resource "maas_vm_host_machine" "kvm" { } } +# +# VM Host 2 +# resource "maas_vm_host" "maas_machine" { type = "virsh" machine = maas_machine.virsh_vm3.hostname @@ -31,10 +37,6 @@ resource "maas_vm_host_machine" "maas_machine_1" { name = "eth0" subnet_cidr = data.maas_subnet.pxe.cidr } - network_interfaces { - name = "eth1" - subnet_cidr = data.maas_subnet.vid10.cidr - } storage_disks { size_gigabytes = 10 diff --git a/examples/3-tags.tf b/examples/3-tags.tf index 9b61a07a..f51fc003 100644 --- a/examples/3-tags.tf +++ b/examples/3-tags.tf @@ -1,6 +1,6 @@ resource "maas_tag" "kvm" { name = "kvm" - machine_ids = [ + machines = [ maas_machine.virsh_vm1.id, maas_machine.virsh_vm2.id, maas_vm_host_machine.kvm[0].id, @@ -10,7 +10,7 @@ resource "maas_tag" "kvm" { resource "maas_tag" "virtual" { name = "virtual" - machine_ids = [ + machines = [ maas_machine.virsh_vm1.id, maas_machine.virsh_vm2.id, maas_vm_host_machine.kvm[0].id, @@ -20,7 +20,7 @@ resource "maas_tag" "virtual" { resource "maas_tag" "ubuntu" { name = "ubuntu" - machine_ids = [ + machines = [ maas_machine.virsh_vm1.id, maas_machine.virsh_vm2.id, maas_vm_host_machine.kvm[0].id, diff --git a/go.mod b/go.mod index 36c3140b..bfb6addd 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,6 @@ go 1.16 require ( github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/terraform-plugin-sdk/v2 v2.6.1 - github.com/ionutbalutoiu/gomaasclient v0.0.0-20210629144238-745a2ee458bd + github.com/ionutbalutoiu/gomaasclient v0.0.0-20210702152754-2e8e72fa2af2 github.com/stretchr/testify v1.7.0 ) diff --git a/go.sum b/go.sum index 2f92306b..218f6ff3 100644 --- a/go.sum +++ b/go.sum @@ -204,8 +204,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/ionutbalutoiu/gomaasclient v0.0.0-20210629144238-745a2ee458bd h1:4QUhuJTuh3wENOU3kAFpme4dQ8gN5b/dtlPGXTfYqMs= -github.com/ionutbalutoiu/gomaasclient v0.0.0-20210629144238-745a2ee458bd/go.mod h1:bj39184ChgZMBH01zYHoUV2h4mSmAYisQSHs/sRzYQE= +github.com/ionutbalutoiu/gomaasclient v0.0.0-20210702152754-2e8e72fa2af2 h1:MdFWC4FH1L6hXR/UwZCKJdOpsN5CRzmJ9/ssjt3KTa8= +github.com/ionutbalutoiu/gomaasclient v0.0.0-20210702152754-2e8e72fa2af2/go.mod h1:bj39184ChgZMBH01zYHoUV2h4mSmAYisQSHs/sRzYQE= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= diff --git a/maas/data_source_maas_fabric.go b/maas/data_source_maas_fabric.go index b0f4302f..2336cfa2 100644 --- a/maas/data_source_maas_fabric.go +++ b/maas/data_source_maas_fabric.go @@ -25,19 +25,11 @@ func dataSourceMaasFabric() *schema.Resource { func dataSourceFabricRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*client.Client) - fabrics, err := client.Fabrics.Get() + fabric, err := getFabric(client, d.Get("name").(string)) if err != nil { return diag.FromErr(err) } + d.SetId(fmt.Sprintf("%v", fabric.ID)) - fabricName := d.Get("name").(string) - for _, fabric := range fabrics { - if fabric.Name != fabricName { - continue - } - d.SetId(fmt.Sprintf("%v", fabric.ID)) - return nil - } - - return diag.FromErr(fmt.Errorf("could not find matching fabric")) + return nil } diff --git a/maas/data_source_maas_subnet.go b/maas/data_source_maas_subnet.go index b90c5fe6..fd96a893 100644 --- a/maas/data_source_maas_subnet.go +++ b/maas/data_source_maas_subnet.go @@ -18,24 +18,32 @@ func dataSourceMaasSubnet() *schema.Resource { Type: schema.TypeString, Required: true, }, - "vlan_id": { - Type: schema.TypeInt, - Optional: true, + "fabric": { + Type: schema.TypeString, + Computed: true, }, "vid": { Type: schema.TypeInt, Computed: true, }, - "fabric": { + "name": { Type: schema.TypeString, Computed: true, }, - "name": { - Type: schema.TypeString, + "rdns_mode": { + Type: schema.TypeInt, Computed: true, }, - "space": { - Type: schema.TypeString, + "allow_dns": { + Type: schema.TypeBool, + Computed: true, + }, + "allow_proxy": { + Type: schema.TypeBool, + Computed: true, + }, + "managed": { + Type: schema.TypeBool, Computed: true, }, "gateway_ip": { @@ -43,16 +51,12 @@ func dataSourceMaasSubnet() *schema.Resource { Computed: true, }, "dns_servers": { - Type: schema.TypeSet, + Type: schema.TypeList, Computed: true, Elem: &schema.Schema{ Type: schema.TypeString, }, }, - "rdns_mode": { - Type: schema.TypeInt, - Computed: true, - }, }, } } @@ -60,55 +64,33 @@ func dataSourceMaasSubnet() *schema.Resource { func dataSourceSubnetRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*client.Client) - subnets, err := client.Subnets.Get() + subnet, err := getSubnet(client, d.Get("cidr").(string)) if err != nil { return diag.FromErr(err) } - - cidr := d.Get("cidr").(string) - vlanId, vlanIdOk := d.GetOk("vlan_id") - - for _, subnet := range subnets { - if cidr != subnet.CIDR { - continue - } - if vlanIdOk { - if vlanId.(int) != subnet.VLAN.ID { - continue - } - } - if err := d.Set("vid", subnet.VLAN.VID); err != nil { - return diag.FromErr(err) - } - if err := d.Set("fabric", subnet.VLAN.Fabric); err != nil { - return diag.FromErr(err) - } - if err := d.Set("name", subnet.Name); err != nil { - return diag.FromErr(err) - } - if err := d.Set("space", subnet.Space); err != nil { - return diag.FromErr(err) - } - gatewayIp := subnet.GatewayIP.String() - if gatewayIp == "" { - gatewayIp = "" - } - if err := d.Set("gateway_ip", gatewayIp); err != nil { - return diag.FromErr(err) - } - dnsServers := make([]string, len(subnet.DNSServers)) - for i, ip := range subnet.DNSServers { - dnsServers[i] = ip.String() - } - if err := d.Set("dns_servers", dnsServers); err != nil { - return diag.FromErr(err) - } - if err := d.Set("rdns_mode", subnet.RDNSMode); err != nil { - return diag.FromErr(err) - } - d.SetId(fmt.Sprintf("%v", subnet.ID)) - return nil + gatewayIp := subnet.GatewayIP.String() + if gatewayIp == "" { + gatewayIp = "" + } + dnsServers := make([]string, len(subnet.DNSServers)) + for i, ip := range subnet.DNSServers { + dnsServers[i] = ip.String() + } + tfState := map[string]interface{}{ + "id": fmt.Sprintf("%v", subnet.ID), + "fabric": subnet.VLAN.Fabric, + "vid": subnet.VLAN.VID, + "name": subnet.Name, + "rdns_mode": subnet.RDNSMode, + "allow_dns": subnet.AllowDNS, + "allow_proxy": subnet.AllowProxy, + "managed": subnet.Managed, + "gateway_ip": gatewayIp, + "dns_servers": dnsServers, + } + if err := setTerraformState(d, tfState); err != nil { + return diag.FromErr(err) } - return diag.FromErr(fmt.Errorf("could not find matching subnet")) + return nil } diff --git a/maas/data_source_maas_vlan.go b/maas/data_source_maas_vlan.go index b590e349..bc5cf95e 100644 --- a/maas/data_source_maas_vlan.go +++ b/maas/data_source_maas_vlan.go @@ -14,18 +14,22 @@ func dataSourceMaasVlan() *schema.Resource { ReadContext: dataSourceVlanRead, Schema: map[string]*schema.Schema{ - "fabric_id": { - Type: schema.TypeInt, + "fabric": { + Type: schema.TypeString, Required: true, }, - "vid": { - Type: schema.TypeInt, + "vlan": { + Type: schema.TypeString, Required: true, }, "mtu": { Type: schema.TypeInt, Computed: true, }, + "dhcp_on": { + Type: schema.TypeBool, + Computed: true, + }, "name": { Type: schema.TypeString, Computed: true, @@ -41,32 +45,24 @@ func dataSourceMaasVlan() *schema.Resource { func dataSourceVlanRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*client.Client) - fabricID := d.Get("fabric_id").(int) - vlans, err := client.VLANs.Get(fabricID) + fabric, err := getFabric(client, d.Get("fabric").(string)) if err != nil { return diag.FromErr(err) } - - vid := d.Get("vid").(int) - for _, vlan := range vlans { - if vlan.FabricID != fabricID { - continue - } - if vlan.VID != vid { - continue - } - if err := d.Set("mtu", vlan.MTU); err != nil { - return diag.FromErr(err) - } - if err := d.Set("name", vlan.Name); err != nil { - return diag.FromErr(err) - } - if err := d.Set("space", vlan.Space); err != nil { - return diag.FromErr(err) - } - d.SetId(fmt.Sprintf("%v", vlan.ID)) - return nil + vlan, err := getVlan(client, fabric.ID, d.Get("vlan").(string)) + if err != nil { + return diag.FromErr(err) + } + tfState := map[string]interface{}{ + "id": fmt.Sprintf("%v", vlan.ID), + "mtu": vlan.MTU, + "dhcp_on": vlan.DHCPOn, + "name": vlan.Name, + "space": vlan.Space, + } + if err := setTerraformState(d, tfState); err != nil { + return diag.FromErr(err) } - return diag.FromErr(fmt.Errorf("could not find matching VLAN")) + return nil } diff --git a/maas/resource_maas_block_device.go b/maas/resource_maas_block_device.go index 9f670920..8fae436a 100644 --- a/maas/resource_maas_block_device.go +++ b/maas/resource_maas_block_device.go @@ -12,11 +12,6 @@ import ( "github.com/ionutbalutoiu/gomaasclient/entity" ) -var ( - defaultBlockSize = 512 - defaultIsBootDevice = false -) - func resourceMaasBlockDevice() *schema.Resource { return &schema.Resource{ CreateContext: resourceBlockDeviceCreate, @@ -30,34 +25,24 @@ func resourceMaasBlockDevice() *schema.Resource { return nil, fmt.Errorf("unexpected format of ID (%q), expected MACHINE:BLOCK_DEVICE", d.Id()) } client := m.(*client.Client) - machine, err := findMachine(client, idParts[0]) + machine, err := getMachine(client, idParts[0]) if err != nil { return nil, err } - blockDevice, err := findBlockDevice(client, machine.SystemID, idParts[1]) + blockDevice, err := getBlockDevice(client, machine.SystemID, idParts[1]) if err != nil { return nil, err } - if blockDevice == nil { - return nil, fmt.Errorf("block device (%s) was not found on machine (%s)", idParts[1], machine.Hostname) - } - if err := d.Set("machine", machine.SystemID); err != nil { - return nil, err - } - if err := d.Set("name", blockDevice.Name); err != nil { - return nil, err + tfState := map[string]interface{}{ + "id": fmt.Sprintf("%v", blockDevice.ID), + "machine": machine.SystemID, + "name": blockDevice.Name, + "size_gigabytes": int(blockDevice.Size / (1024 * 1024 * 1024)), + "block_size": blockDevice.BlockSize, } - sizeGigabytes := blockDevice.Size / (1024 * 1024 * 1024) - if err := d.Set("size_gigabytes", int(sizeGigabytes)); err != nil { + if err := setTerraformState(d, tfState); err != nil { return nil, err } - if err := d.Set("block_size", defaultBlockSize); err != nil { - return nil, err - } - if err := d.Set("is_boot_device", defaultIsBootDevice); err != nil { - return nil, err - } - d.SetId(fmt.Sprintf("%v", blockDevice.ID)) return []*schema.ResourceData{d}, nil }, }, @@ -79,12 +64,11 @@ func resourceMaasBlockDevice() *schema.Resource { "block_size": { Type: schema.TypeInt, Optional: true, - Default: defaultBlockSize, + Default: 512, }, "is_boot_device": { Type: schema.TypeBool, Optional: true, - Default: defaultIsBootDevice, }, "partitions": { Type: schema.TypeList, @@ -99,7 +83,6 @@ func resourceMaasBlockDevice() *schema.Resource { "bootable": { Type: schema.TypeBool, Optional: true, - Default: false, }, "tags": { Type: schema.TypeSet, @@ -176,7 +159,7 @@ func resourceMaasBlockDevice() *schema.Resource { func resourceBlockDeviceCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*client.Client) - machine, err := findMachine(client, d.Get("machine").(string)) + machine, err := getMachine(client, d.Get("machine").(string)) if err != nil { return diag.FromErr(err) } @@ -202,7 +185,7 @@ func resourceBlockDeviceRead(ctx context.Context, d *schema.ResourceData, m inte if err != nil { return diag.FromErr(err) } - machine, err := findMachine(client, d.Get("machine").(string)) + machine, err := getMachine(client, d.Get("machine").(string)) if err != nil { return diag.FromErr(err) } @@ -210,26 +193,16 @@ func resourceBlockDeviceRead(ctx context.Context, d *schema.ResourceData, m inte if err != nil { return diag.FromErr(err) } - - if err := d.Set("model", blockDevice.Model); err != nil { - return diag.FromErr(err) - } - if err := d.Set("serial", blockDevice.Serial); err != nil { - return diag.FromErr(err) - } - if err := d.Set("id_path", blockDevice.IDPath); err != nil { - return diag.FromErr(err) - } - if err := d.Set("tags", blockDevice.Tags); err != nil { - return diag.FromErr(err) - } - if err := d.Set("uuid", blockDevice.UUID); err != nil { - return diag.FromErr(err) - } - if err := d.Set("path", blockDevice.IDPath); err != nil { - return diag.FromErr(err) + tfState := map[string]interface{}{ + "partitions": getBlockDevicePartitionsTFState(blockDevice), + "model": blockDevice.Model, + "serial": blockDevice.Serial, + "id_path": blockDevice.IDPath, + "tags": blockDevice.Tags, + "uuid": blockDevice.UUID, + "path": blockDevice.Path, } - if err := setBlockDevicePartitionsTFState(d, blockDevice); err != nil { + if err := setTerraformState(d, tfState); err != nil { return diag.FromErr(err) } @@ -243,7 +216,7 @@ func resourceBlockDeviceUpdate(ctx context.Context, d *schema.ResourceData, m in if err != nil { return diag.FromErr(err) } - machine, err := findMachine(client, d.Get("machine").(string)) + machine, err := getMachine(client, d.Get("machine").(string)) if err != nil { return diag.FromErr(err) } @@ -251,20 +224,15 @@ func resourceBlockDeviceUpdate(ctx context.Context, d *schema.ResourceData, m in if err != nil { return diag.FromErr(err) } - if err := setBlockDeviceTags(client, d, blockDevice); err != nil { return diag.FromErr(err) } - - if p, ok := d.GetOk("is_boot_device"); ok { - if p.(bool) { - if err := client.BlockDevice.SetBootDisk(machine.SystemID, id); err != nil { - return diag.FromErr(err) - } + if p, ok := d.GetOk("is_boot_device"); ok && p.(bool) { + if err := client.BlockDevice.SetBootDisk(machine.SystemID, id); err != nil { + return diag.FromErr(err) } } - - if err := createBlockDevicePartitions(client, d, blockDevice); err != nil { + if err := updateBlockDevicePartitions(client, d, blockDevice); err != nil { return diag.FromErr(err) } @@ -278,7 +246,7 @@ func resourceBlockDeviceDelete(ctx context.Context, d *schema.ResourceData, m in if err != nil { return diag.FromErr(err) } - machine, err := findMachine(client, d.Get("machine").(string)) + machine, err := getMachine(client, d.Get("machine").(string)) if err != nil { return diag.FromErr(err) } @@ -290,21 +258,14 @@ func resourceBlockDeviceDelete(ctx context.Context, d *schema.ResourceData, m in } func getBlockDeviceParams(d *schema.ResourceData) *entity.BlockDeviceParams { - params := entity.BlockDeviceParams{ + return &entity.BlockDeviceParams{ Name: d.Get("name").(string), Size: d.Get("size_gigabytes").(int) * 1024 * 1024 * 1024, BlockSize: d.Get("block_size").(int), + Model: d.Get("model").(string), + Serial: d.Get("serial").(string), + IDPath: d.Get("id_path").(string), } - if p, ok := d.GetOk("model"); ok { - params.Model = p.(string) - } - if p, ok := d.GetOk("serial"); ok { - params.Serial = p.(string) - } - if p, ok := d.GetOk("id_path"); ok { - params.IDPath = p.(string) - } - return ¶ms } func findBlockDevice(client *client.Client, machineID string, identifier string) (*entity.BlockDevice, error) { @@ -312,16 +273,25 @@ func findBlockDevice(client *client.Client, machineID string, identifier string) if err != nil { return nil, err } - for _, b := range blockDevices { if fmt.Sprintf("%v", b.ID) == identifier || b.Name == identifier || b.IDPath == identifier || b.Path == identifier { return &b, nil } } - return nil, nil } +func getBlockDevice(client *client.Client, machineID string, identifier string) (*entity.BlockDevice, error) { + blockDevice, err := findBlockDevice(client, machineID, identifier) + if err != nil { + return nil, err + } + if blockDevice == nil { + return nil, fmt.Errorf("block device (%s) was not found on machine (%s)", identifier, machineID) + } + return blockDevice, nil +} + func setBlockDeviceTags(client *client.Client, d *schema.ResourceData, blockDevice *entity.BlockDevice) error { p, ok := d.GetOk("tags") if !ok { @@ -347,28 +317,25 @@ func setBlockDeviceTags(client *client.Client, d *schema.ResourceData, blockDevi return nil } -func setBlockDevicePartitionsTFState(d *schema.ResourceData, blockDevice *entity.BlockDevice) error { +func getBlockDevicePartitionsTFState(blockDevice *entity.BlockDevice) []map[string]interface{} { partitions := make([]map[string]interface{}, len(blockDevice.Partitions)) for i, p := range blockDevice.Partitions { part := map[string]interface{}{ "size_gigabytes": int(p.Size / (1024 * 1024 * 1024)), "bootable": p.Bootable, "tags": p.Tags, - "path": p.Path, "fs_type": p.FileSystem.FSType, "label": p.FileSystem.Label, "mount_point": p.FileSystem.MountPoint, "mount_options": p.FileSystem.MountOptions, + "path": p.Path, } partitions[i] = part } - if err := d.Set("partitions", partitions); err != nil { - return err - } - return nil + return partitions } -func createBlockDevicePartitions(client *client.Client, d *schema.ResourceData, blockDevice *entity.BlockDevice) error { +func updateBlockDevicePartitions(client *client.Client, d *schema.ResourceData, blockDevice *entity.BlockDevice) error { p, ok := d.GetOk("partitions") if !ok { return nil diff --git a/maas/resource_maas_fabric.go b/maas/resource_maas_fabric.go index 2add3103..28b79e2e 100644 --- a/maas/resource_maas_fabric.go +++ b/maas/resource_maas_fabric.go @@ -60,8 +60,7 @@ func resourceFabricRead(ctx context.Context, d *schema.ResourceData, m interface if err != nil { return diag.FromErr(err) } - _, err = client.Fabric.Get(id) - if err != nil { + if _, err := client.Fabric.Get(id); err != nil { return diag.FromErr(err) } @@ -75,8 +74,7 @@ func resourceFabricUpdate(ctx context.Context, d *schema.ResourceData, m interfa if err != nil { return diag.FromErr(err) } - _, err = client.Fabric.Update(id, getFabricParams(d)) - if err != nil { + if _, err := client.Fabric.Update(id, getFabricParams(d)); err != nil { return diag.FromErr(err) } @@ -90,8 +88,7 @@ func resourceFabricDelete(ctx context.Context, d *schema.ResourceData, m interfa if err != nil { return diag.FromErr(err) } - err = client.Fabric.Delete(id) - if err != nil { + if err := client.Fabric.Delete(id); err != nil { return diag.FromErr(err) } diff --git a/maas/resource_maas_instance.go b/maas/resource_maas_instance.go index fcba8e09..4cb196a4 100644 --- a/maas/resource_maas_instance.go +++ b/maas/resource_maas_instance.go @@ -3,11 +3,10 @@ package maas import ( "context" "fmt" - "net" - "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/ionutbalutoiu/gomaasclient/client" "github.com/ionutbalutoiu/gomaasclient/entity" ) @@ -20,13 +19,14 @@ func resourceMaasInstance() *schema.Resource { Importer: &schema.ResourceImporter{ StateContext: func(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { client := m.(*client.Client) - machine, err := findMachine(client, d.Id()) + machine, err := getMachine(client, d.Id()) if err != nil { return nil, err } if machine.StatusName != "Deployed" { return nil, fmt.Errorf("machine '%s' needs to be already deployed to be imported as maas_instance resource", machine.Hostname) } + d.SetId(machine.SystemID) return []*schema.ResourceData{d}, nil }, }, @@ -94,7 +94,7 @@ func resourceMaasInstance() *schema.Resource { }, }, "network_interfaces": { - Type: schema.TypeList, + Type: schema.TypeSet, Optional: true, ForceNew: true, Elem: &schema.Resource{ @@ -108,16 +108,9 @@ func resourceMaasInstance() *schema.Resource { Optional: true, }, "ip_address": { - Type: schema.TypeString, - Optional: true, - Default: "", - ValidateDiagFunc: func(value interface{}, path cty.Path) diag.Diagnostics { - v := value.(string) - if ip := net.ParseIP(v); ip == nil { - return diag.FromErr(fmt.Errorf("ip_address must be a valid IP address (got '%s')", v)) - } - return nil - }, + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.IsIPAddress), }, }, }, @@ -206,34 +199,22 @@ func resourceInstanceRead(ctx context.Context, d *schema.ResourceData, m interfa if err != nil { return diag.FromErr(err) } - // Set Terraform state - if err := d.Set("fqdn", machine.FQDN); err != nil { - return diag.FromErr(err) - } - if err := d.Set("hostname", machine.Hostname); err != nil { - return diag.FromErr(err) - } - if err := d.Set("zone", machine.Zone.Name); err != nil { - return diag.FromErr(err) - } - if err := d.Set("pool", machine.Pool.Name); err != nil { - return diag.FromErr(err) - } - if err := d.Set("tags", machine.TagNames); err != nil { - return diag.FromErr(err) - } - if err := d.Set("cpu_count", machine.CPUCount); err != nil { - return diag.FromErr(err) - } - if err := d.Set("memory", machine.Memory); err != nil { - return diag.FromErr(err) - } ipAddresses := make([]string, len(machine.IPAddresses)) for i, ip := range machine.IPAddresses { ipAddresses[i] = ip.String() } - if err := d.Set("ip_addresses", ipAddresses); err != nil { + tfState := map[string]interface{}{ + "fqdn": machine.FQDN, + "hostname": machine.Hostname, + "zone": machine.Zone.Name, + "pool": machine.Pool.Name, + "tags": machine.TagNames, + "cpu_count": machine.CPUCount, + "memory": machine.Memory, + "ip_addresses": ipAddresses, + } + if err := setTerraformState(d, tfState); err != nil { return diag.FromErr(err) } @@ -288,76 +269,48 @@ func getMachineDeployParams(d *schema.ResourceData) *entity.MachineDeployParams } func configureInstanceNetworkInterfaces(client *client.Client, d *schema.ResourceData, machine *entity.Machine) error { - p, ok := d.GetOk("network_interfaces") - if !ok { - return nil - } - machineNics, err := client.NetworkInterfaces.Get(machine.SystemID) - if err != nil { - return err - } - subnets, err := client.Subnets.Get() - if err != nil { - return err - } - nics := p.([]interface{}) - for _, nic := range nics { - n := nic.(map[string]interface{}) + for _, networkInterface := range d.Get("network_interfaces").(*schema.Set).List() { + n := networkInterface.(map[string]interface{}) // Find the machine network interface name := n["name"].(string) - var nicFound *entity.NetworkInterface = nil - for _, machineNic := range machineNics { - if machineNic.Name == name { - nicFound = &machineNic - break - } - } - if nicFound == nil { - return fmt.Errorf("network interface '%s' was not found on allocated instance '%s'", name, machine.FQDN) + nic, err := getNetworkInterface(client, machine.SystemID, name) + if err != nil { + return err } - subnetCidr := n["subnet_cidr"].(string) + // Validate the given network configs + subnetCIDR := n["subnet_cidr"].(string) ipAddress := n["ip_address"].(string) - if subnetCidr == "" { - if ipAddress == "" { - // Clear existing network interface links - _, err := client.NetworkInterface.Disconnect(machine.SystemID, nicFound.ID) - if err != nil { - return err - } - continue - } else { - return fmt.Errorf("network interface '%s': the 'subnet_cidr' is required when 'ip_address' is set", name) + if subnetCIDR == "" { + if ipAddress != "" { + return fmt.Errorf("network interface (%s): 'subnet_cidr' is required when 'ip_address' is set", name) } + // Clear existing network interface links + // This will leave the network interface disconnected + if _, err := client.NetworkInterface.Disconnect(machine.SystemID, nic.ID); err != nil { + return err + } + continue } // Find the subnet - var subnetFound *entity.Subnet = nil - for _, subnet := range subnets { - if subnet.CIDR == subnetCidr { - subnetFound = &subnet - break - } + subnet, err := getSubnet(client, subnetCIDR) + if err != nil { + return err } - if subnetFound == nil { - return fmt.Errorf("subnet with CIDR '%s' was not found", subnetCidr) + // Clear existing network interface links + if _, err := client.NetworkInterface.Disconnect(machine.SystemID, nic.ID); err != nil { + return err } - // Prepare the network interface link parameters + // Create new network interface link mode := "AUTO" if ipAddress != "" { mode = "STATIC" } params := entity.NetworkInterfaceLinkParams{ Mode: mode, - Subnet: subnetFound.ID, + Subnet: subnet.ID, IPAddress: ipAddress, } - // Clear existing network interface links - _, err := client.NetworkInterface.Disconnect(machine.SystemID, nicFound.ID) - if err != nil { - return err - } - // Create new network interface link - _, err = client.NetworkInterface.LinkSubnet(machine.SystemID, nicFound.ID, ¶ms) - if err != nil { + if _, err = client.NetworkInterface.LinkSubnet(machine.SystemID, nic.ID, ¶ms); err != nil { return err } } diff --git a/maas/resource_maas_machine.go b/maas/resource_maas_machine.go index 54c3553d..21d1a066 100644 --- a/maas/resource_maas_machine.go +++ b/maas/resource_maas_machine.go @@ -22,27 +22,24 @@ func resourceMaasMachine() *schema.Resource { Importer: &schema.ResourceImporter{ StateContext: func(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { client := m.(*client.Client) - machine, err := findMachine(client, d.Id()) + machine, err := getMachine(client, d.Id()) if err != nil { return nil, err } - if err := d.Set("power_type", machine.PowerType); err != nil { - return nil, err - } powerParams, err := client.Machine.GetPowerParameters(machine.SystemID) if err != nil { return nil, err } - if err := d.Set("power_parameters", powerParams); err != nil { - return nil, err - } - if err := d.Set("pxe_mac_address", machine.BootInterface.MACAddress); err != nil { - return nil, err + tfState := map[string]interface{}{ + "id": machine.SystemID, + "power_type": machine.PowerType, + "power_parameters": powerParams, + "pxe_mac_address": machine.BootInterface.MACAddress, + "architecture": machine.Architecture, } - if err := d.Set("architecture", machine.Architecture); err != nil { + if err := setTerraformState(d, tfState); err != nil { return nil, err } - d.SetId(machine.SystemID) return []*schema.ResourceData{d}, nil }, }, @@ -102,7 +99,7 @@ func resourceMachineCreate(ctx context.Context, d *schema.ResourceData, m interf client := m.(*client.Client) // Create MAAS machine - machine, err := client.Machines.Create(getMachineCreateParams(d), getMachinePowerParams(d)) + machine, err := client.Machines.Create(getMachineParams(d), getMachinePowerParams(d)) if err != nil { return diag.FromErr(err) } @@ -130,19 +127,15 @@ func resourceMachineRead(ctx context.Context, d *schema.ResourceData, m interfac } // Set Terraform state - if err := d.Set("min_hwe_kernel", machine.MinHWEKernel); err != nil { - return diag.FromErr(err) - } - if err := d.Set("hostname", machine.Hostname); err != nil { - return diag.FromErr(err) - } - if err := d.Set("domain", machine.Domain.Name); err != nil { - return diag.FromErr(err) - } - if err := d.Set("zone", machine.Zone.Name); err != nil { - return diag.FromErr(err) - } - if err := d.Set("pool", machine.Pool.Name); err != nil { + tfState := map[string]interface{}{ + "architecture": machine.Architecture, + "min_hwe_kernel": machine.MinHWEKernel, + "hostname": machine.Hostname, + "domain": machine.Domain.Name, + "zone": machine.Zone.Name, + "pool": machine.Pool.Name, + } + if err := setTerraformState(d, tfState); err != nil { return diag.FromErr(err) } @@ -157,8 +150,7 @@ func resourceMachineUpdate(ctx context.Context, d *schema.ResourceData, m interf if err != nil { return diag.FromErr(err) } - _, err = client.Machine.Update(machine.SystemID, getMachineUpdateParams(d, machine), getMachinePowerParams(d)) - if err != nil { + if _, err := client.Machine.Update(machine.SystemID, getMachineParams(d), getMachinePowerParams(d)); err != nil { return diag.FromErr(err) } @@ -169,8 +161,7 @@ func resourceMachineDelete(ctx context.Context, d *schema.ResourceData, m interf client := m.(*client.Client) // Delete machine - err := client.Machine.Delete(d.Id()) - if err != nil { + if err := client.Machine.Delete(d.Id()); err != nil { return diag.FromErr(err) } @@ -186,60 +177,18 @@ func getMachinePowerParams(d *schema.ResourceData) map[string]string { return params } -func getMachineCreateParams(d *schema.ResourceData) *entity.MachineParams { - params := entity.MachineParams{ +func getMachineParams(d *schema.ResourceData) *entity.MachineParams { + return &entity.MachineParams{ + Commission: true, PowerType: d.Get("power_type").(string), PXEMacAddress: d.Get("pxe_mac_address").(string), - Commission: true, - } - - if p, ok := d.GetOk("architecture"); ok { - params.Architecture = p.(string) - } - if p, ok := d.GetOk("min_hwe_kernel"); ok { - params.MinHWEKernel = p.(string) - } - if p, ok := d.GetOk("hostname"); ok { - params.Hostname = p.(string) - } - if p, ok := d.GetOk("domain"); ok { - params.Domain = p.(string) - } - - return ¶ms -} - -func getMachineUpdateParams(d *schema.ResourceData, machine *entity.Machine) *entity.MachineParams { - params := entity.MachineParams{ - PowerType: d.Get("power_type").(string), - CPUCount: machine.CPUCount, - Memory: machine.Memory, - SwapSize: machine.SwapSize, - Architecture: machine.Architecture, - MinHWEKernel: machine.MinHWEKernel, - Description: machine.Description, - } - - if p, ok := d.GetOk("architecture"); ok { - params.Architecture = p.(string) - } - if p, ok := d.GetOk("min_hwe_kernel"); ok { - params.MinHWEKernel = p.(string) - } - if p, ok := d.GetOk("hostname"); ok { - params.Hostname = p.(string) - } - if p, ok := d.GetOk("domain"); ok { - params.Domain = p.(string) - } - if p, ok := d.GetOk("zone"); ok { - params.Zone = p.(string) + Architecture: d.Get("architecture").(string), + MinHWEKernel: d.Get("min_hwe_kernel").(string), + Hostname: d.Get("hostname").(string), + Domain: d.Get("domain").(string), + Zone: d.Get("zone").(string), + Pool: d.Get("pool").(string), } - if p, ok := d.GetOk("pool"); ok { - params.Pool = p.(string) - } - - return ¶ms } func getMachineStatusFunc(client *client.Client, systemId string) resource.StateRefreshFunc { @@ -270,17 +219,15 @@ func waitForMachineStatus(ctx context.Context, client *client.Client, systemID s return result.(*entity.Machine), nil } -func findMachine(client *client.Client, identifier string) (*entity.Machine, error) { +func getMachine(client *client.Client, identifier string) (*entity.Machine, error) { machines, err := client.Machines.Get() if err != nil { return nil, err } - for _, m := range machines { if m.SystemID == identifier || m.Hostname == identifier || m.FQDN == identifier { return &m, nil } } - - return nil, fmt.Errorf("machine '%s' not found", identifier) + return nil, fmt.Errorf("machine (%s) not found", identifier) } diff --git a/maas/resource_maas_network_interface_link.go b/maas/resource_maas_network_interface_link.go index 05f835df..ae9c37f8 100644 --- a/maas/resource_maas_network_interface_link.go +++ b/maas/resource_maas_network_interface_link.go @@ -3,12 +3,11 @@ package maas import ( "context" "fmt" - "net" "strconv" - "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/ionutbalutoiu/gomaasclient/client" "github.com/ionutbalutoiu/gomaasclient/entity" ) @@ -21,52 +20,40 @@ func resourceMaasNetworkInterfaceLink() *schema.Resource { DeleteContext: resourceNetworkInterfaceLinkDelete, Schema: map[string]*schema.Schema{ - "machine_id": { + "machine": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "network_interface_id": { - Type: schema.TypeInt, + "network_interface": { + Type: schema.TypeString, Required: true, ForceNew: true, }, - "subnet_id": { - Type: schema.TypeInt, + "subnet": { + Type: schema.TypeString, Required: true, ForceNew: true, }, "mode": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Default: "AUTO", - ValidateDiagFunc: func(value interface{}, path cty.Path) diag.Diagnostics { - v := value.(string) - if !(v == "AUTO" || v == "DHCP" || v == "STATIC") { - return diag.FromErr(fmt.Errorf("mode must be 'AUTO', 'DHCP', or 'STATIC' (got '%s')", v)) - } - return nil - }, - }, - "ip_address": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Computed: true, - ValidateDiagFunc: func(value interface{}, path cty.Path) diag.Diagnostics { - v := value.(string) - if ip := net.ParseIP(v); ip == nil { - return diag.FromErr(fmt.Errorf("ip_address must be a valid IP address (got '%s')", v)) - } - return nil - }, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "AUTO", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"AUTO", "DHCP", "STATIC", "LINK_UP"}, false)), }, "default_gateway": { Type: schema.TypeBool, Optional: true, Default: false, }, + "ip_address": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.IsIPAddress), + }, }, } } @@ -74,13 +61,20 @@ func resourceMaasNetworkInterfaceLink() *schema.Resource { func resourceNetworkInterfaceLinkCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*client.Client) - // Get params for the create operation - machineId := d.Get("machine_id").(string) - networkInterfaceId := d.Get("network_interface_id").(int) - params := getNetworkInterfaceLinkParams(client, d) - // Create network interface link - link, err := createNetworkInterfaceLink(client, machineId, networkInterfaceId, params) + machine, err := getMachine(client, d.Get("machine").(string)) + if err != nil { + return diag.FromErr(err) + } + networkInterface, err := getNetworkInterface(client, machine.SystemID, d.Get("network_interface").(string)) + if err != nil { + return diag.FromErr(err) + } + subnet, err := getSubnet(client, d.Get("subnet").(string)) + if err != nil { + return diag.FromErr(err) + } + link, err := createNetworkInterfaceLink(client, machine.SystemID, networkInterface.ID, getNetworkInterfaceLinkParams(d, subnet.ID)) if err != nil { return diag.FromErr(err) } @@ -95,15 +89,21 @@ func resourceNetworkInterfaceLinkRead(ctx context.Context, d *schema.ResourceDat client := m.(*client.Client) // Get params for the read operation - linkId, err := strconv.Atoi(d.Id()) + linkID, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + machine, err := getMachine(client, d.Get("machine").(string)) + if err != nil { + return diag.FromErr(err) + } + networkInterface, err := getNetworkInterface(client, machine.SystemID, d.Get("network_interface").(string)) if err != nil { return diag.FromErr(err) } - machineId := d.Get("machine_id").(string) - networkInterfaceId := d.Get("network_interface_id").(int) // Get the network interface link - link, err := getNetworkInterfaceLink(client, machineId, networkInterfaceId, linkId) + link, err := getNetworkInterfaceLink(client, machine.SystemID, networkInterface.ID, linkID) if err != nil { return diag.FromErr(err) } @@ -120,22 +120,25 @@ func resourceNetworkInterfaceLinkUpdate(ctx context.Context, d *schema.ResourceD client := m.(*client.Client) // Get params for the update operation - linkId, err := strconv.Atoi(d.Id()) + linkID, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + machine, err := getMachine(client, d.Get("machine").(string)) + if err != nil { + return diag.FromErr(err) + } + networkInterface, err := getNetworkInterface(client, machine.SystemID, d.Get("network_interface").(string)) if err != nil { return diag.FromErr(err) } - machineId := d.Get("machine_id").(string) - networkInterfaceId := d.Get("network_interface_id").(int) - params := getNetworkInterfaceLinkParams(client, d) // Run update operation - _, err = client.Machine.ClearDefaultGateways(machineId) - if err != nil { + if _, err := client.Machine.ClearDefaultGateways(machine.SystemID); err != nil { return diag.FromErr(err) } - if params.DefaultGateway { - _, err = client.NetworkInterface.SetDefaultGateway(machineId, networkInterfaceId, linkId) - if err != nil { + if d.Get("default_gateway").(bool) { + if _, err := client.NetworkInterface.SetDefaultGateway(machine.SystemID, networkInterface.ID, linkID); err != nil { return diag.FromErr(err) } } @@ -147,68 +150,64 @@ func resourceNetworkInterfaceLinkDelete(ctx context.Context, d *schema.ResourceD client := m.(*client.Client) // Get params for the delete operation - linkId, err := strconv.Atoi(d.Id()) + linkID, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + machine, err := getMachine(client, d.Get("machine").(string)) + if err != nil { + return diag.FromErr(err) + } + networkInterface, err := getNetworkInterface(client, machine.SystemID, d.Get("network_interface").(string)) if err != nil { return diag.FromErr(err) } - machineId := d.Get("machine_id").(string) - networkInterfaceId := d.Get("network_interface_id").(int) // Delete the network interface link - err = deleteNetworkInterfaceLink(client, machineId, networkInterfaceId, linkId) - if err != nil { + if err := deleteNetworkInterfaceLink(client, machine.SystemID, networkInterface.ID, linkID); err != nil { return diag.FromErr(err) } return nil } -func getNetworkInterfaceLinkParams(client *client.Client, d *schema.ResourceData) *entity.NetworkInterfaceLinkParams { - params := entity.NetworkInterfaceLinkParams{ - Subnet: d.Get("subnet_id").(int), +func getNetworkInterfaceLinkParams(d *schema.ResourceData, subnetID int) *entity.NetworkInterfaceLinkParams { + return &entity.NetworkInterfaceLinkParams{ + Subnet: subnetID, Mode: d.Get("mode").(string), DefaultGateway: d.Get("default_gateway").(bool), + IPAddress: d.Get("ip_address").(string), } - - if p, ok := d.GetOk("ip_address"); ok { - params.IPAddress = p.(string) - } - - return ¶ms } -func createNetworkInterfaceLink(client *client.Client, machineId string, networkInterfaceId int, params *entity.NetworkInterfaceLinkParams) (*entity.NetworkInterfaceLink, error) { +func createNetworkInterfaceLink(client *client.Client, machineSystemID string, networkInterfaceID int, params *entity.NetworkInterfaceLinkParams) (*entity.NetworkInterfaceLink, error) { // Clear existing links - _, err := client.NetworkInterface.Disconnect(machineId, networkInterfaceId) + _, err := client.NetworkInterface.Disconnect(machineSystemID, networkInterfaceID) if err != nil { return nil, err } - // Create new link - networkInterface, err := client.NetworkInterface.LinkSubnet(machineId, networkInterfaceId, params) + networkInterface, err := client.NetworkInterface.LinkSubnet(machineSystemID, networkInterfaceID, params) if err != nil { return nil, err } - return &networkInterface.Links[0], nil } -func getNetworkInterfaceLink(client *client.Client, machineId string, networkInterfaceId int, linkId int) (*entity.NetworkInterfaceLink, error) { - networkInterface, err := client.NetworkInterface.Get(machineId, networkInterfaceId) +func getNetworkInterfaceLink(client *client.Client, machineSystemID string, networkInterfaceID int, linkID int) (*entity.NetworkInterfaceLink, error) { + networkInterface, err := client.NetworkInterface.Get(machineSystemID, networkInterfaceID) if err != nil { return nil, err } - for _, link := range networkInterface.Links { - if link.ID == linkId { + if link.ID == linkID { return &link, nil } } - - return nil, fmt.Errorf("cannot find link with id '%v'", linkId) + return nil, fmt.Errorf("cannot find link (%v) on the network interface (%v) from machine (%s)", linkID, networkInterfaceID, machineSystemID) } -func deleteNetworkInterfaceLink(client *client.Client, machineId string, networkInterfaceId int, linkId int) (err error) { - _, err = client.NetworkInterface.UnlinkSubnet(machineId, networkInterfaceId, linkId) - return +func deleteNetworkInterfaceLink(client *client.Client, machineSystemID string, networkInterfaceID int, linkID int) error { + _, err := client.NetworkInterface.UnlinkSubnet(machineSystemID, networkInterfaceID, linkID) + return err } diff --git a/maas/resource_maas_network_interface_physical.go b/maas/resource_maas_network_interface_physical.go index b0401be4..5c493462 100644 --- a/maas/resource_maas_network_interface_physical.go +++ b/maas/resource_maas_network_interface_physical.go @@ -12,13 +12,6 @@ import ( "github.com/ionutbalutoiu/gomaasclient/entity" ) -var ( - defaultVLAN = "untagged" - defaultMTU = 1500 - defaultAcceptRA = false - defaultAutoconf = false -) - func resourceMaasNetworkInterfacePhysical() *schema.Resource { return &schema.Resource{ CreateContext: resourceNetworkInterfacePhysicalCreate, @@ -32,45 +25,29 @@ func resourceMaasNetworkInterfacePhysical() *schema.Resource { return nil, fmt.Errorf("unexpected format of ID (%q), expected MACHINE:NETWORK_INTERFACE", d.Id()) } client := m.(*client.Client) - machine, err := findMachine(client, idParts[0]) + machine, err := getMachine(client, idParts[0]) if err != nil { return nil, err } - networkInterface, err := findNetworkInterfacePhysical(client, machine.SystemID, idParts[1]) + n, err := getNetworkInterfacePhysical(client, machine.SystemID, idParts[1]) if err != nil { return nil, err } - if networkInterface == nil { - return nil, fmt.Errorf("physical network interface (%s) was not found on machine (%s)", idParts[1], machine.Hostname) - } - if err := d.Set("machine_id", machine.SystemID); err != nil { - return nil, err - } - if err := d.Set("mac_address", networkInterface.MACAddress); err != nil { - return nil, err - } - if err := d.Set("tags", networkInterface.Tags); err != nil { - return nil, err - } - if err := d.Set("vlan", defaultVLAN); err != nil { - return nil, err - } - if err := d.Set("mtu", defaultMTU); err != nil { - return nil, err - } - if err := d.Set("accept_ra", defaultAcceptRA); err != nil { - return nil, err + tfState := map[string]interface{}{ + "id": fmt.Sprintf("%v", n.ID), + "machine": machine.SystemID, + "mac_address": n.MACAddress, + "vlan": fmt.Sprintf("%v", n.VLAN.ID), } - if err := d.Set("autoconf", defaultAutoconf); err != nil { + if err := setTerraformState(d, tfState); err != nil { return nil, err } - d.SetId(fmt.Sprintf("%v", networkInterface.ID)) return []*schema.ResourceData{d}, nil }, }, Schema: map[string]*schema.Schema{ - "machine_id": { + "machine": { Type: schema.TypeString, Required: true, ForceNew: true, @@ -80,6 +57,10 @@ func resourceMaasNetworkInterfacePhysical() *schema.Resource { Required: true, ForceNew: true, }, + "vlan": { + Type: schema.TypeString, + Optional: true, + }, "name": { Type: schema.TypeString, Optional: true, @@ -88,29 +69,15 @@ func resourceMaasNetworkInterfacePhysical() *schema.Resource { "tags": { Type: schema.TypeSet, Optional: true, + Computed: true, Elem: &schema.Schema{ Type: schema.TypeString, }, }, - "vlan": { - Type: schema.TypeString, - Optional: true, - Default: defaultVLAN, - }, "mtu": { Type: schema.TypeInt, Optional: true, - Default: defaultMTU, - }, - "accept_ra": { - Type: schema.TypeBool, - Optional: true, - Default: defaultAcceptRA, - }, - "autoconf": { - Type: schema.TypeBool, - Optional: true, - Default: defaultAutoconf, + Computed: true, }, }, } @@ -119,19 +86,20 @@ func resourceMaasNetworkInterfacePhysical() *schema.Resource { func resourceNetworkInterfacePhysicalCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*client.Client) - machineId := d.Get("machine_id").(string) - networkInterface, err := findNetworkInterfacePhysical(client, d.Get("machine_id").(string), d.Get("mac_address").(string)) + machine, err := getMachine(client, d.Get("machine").(string)) + if err != nil { + return diag.FromErr(err) + } + networkInterface, err := findNetworkInterfacePhysical(client, machine.SystemID, d.Get("mac_address").(string)) if err != nil { return diag.FromErr(err) } - if networkInterface == nil { - networkInterface, err = client.NetworkInterfaces.CreatePhysical(machineId, getNetworkInterfacePhysicalParams(d)) + networkInterface, err = client.NetworkInterfaces.CreatePhysical(machine.SystemID, getNetworkInterfacePhysicalParams(d)) if err != nil { return diag.FromErr(err) } } - d.SetId(fmt.Sprintf("%v", networkInterface.ID)) return resourceNetworkInterfacePhysicalUpdate(ctx, d, m) @@ -140,17 +108,25 @@ func resourceNetworkInterfacePhysicalCreate(ctx context.Context, d *schema.Resou func resourceNetworkInterfacePhysicalRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*client.Client) - machineId := d.Get("machine_id").(string) + machine, err := getMachine(client, d.Get("machine").(string)) + if err != nil { + return diag.FromErr(err) + } id, err := strconv.Atoi(d.Id()) if err != nil { return diag.FromErr(err) } - networkInterface, err := client.NetworkInterface.Get(machineId, id) + networkInterface, err := client.NetworkInterface.Get(machine.SystemID, id) if err != nil { return diag.FromErr(err) } - if err := d.Set("name", networkInterface.Name); err != nil { + tfState := map[string]interface{}{ + "name": networkInterface.Name, + "tags": networkInterface.Tags, + "mtu": networkInterface.EffectiveMTU, + } + if err := setTerraformState(d, tfState); err != nil { return diag.FromErr(err) } @@ -160,15 +136,17 @@ func resourceNetworkInterfacePhysicalRead(ctx context.Context, d *schema.Resourc func resourceNetworkInterfacePhysicalUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*client.Client) - machineId := d.Get("machine_id").(string) - id, err := strconv.Atoi(d.Id()) + machine, err := getMachine(client, d.Get("machine").(string)) if err != nil { return diag.FromErr(err) } - _, err = client.NetworkInterface.Update(machineId, id, getNetworkInterfacePhysicalParams(d)) + id, err := strconv.Atoi(d.Id()) if err != nil { return diag.FromErr(err) } + if _, err = client.NetworkInterface.Update(machine.SystemID, id, getNetworkInterfacePhysicalParams(d)); err != nil { + return diag.FromErr(err) + } return resourceNetworkInterfacePhysicalRead(ctx, d, m) } @@ -176,12 +154,15 @@ func resourceNetworkInterfacePhysicalUpdate(ctx context.Context, d *schema.Resou func resourceNetworkInterfacePhysicalDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*client.Client) - machineId := d.Get("machine_id").(string) + machine, err := getMachine(client, d.Get("machine").(string)) + if err != nil { + return diag.FromErr(err) + } id, err := strconv.Atoi(d.Id()) if err != nil { return diag.FromErr(err) } - if err := client.NetworkInterface.Delete(machineId, id); err != nil { + if err := client.NetworkInterface.Delete(machine.SystemID, id); err != nil { return diag.FromErr(err) } @@ -189,30 +170,20 @@ func resourceNetworkInterfacePhysicalDelete(ctx context.Context, d *schema.Resou } func getNetworkInterfacePhysicalParams(d *schema.ResourceData) *entity.NetworkInterfacePhysicalParams { - params := entity.NetworkInterfacePhysicalParams{ + return &entity.NetworkInterfacePhysicalParams{ MACAddress: d.Get("mac_address").(string), VLAN: d.Get("vlan").(string), + Name: d.Get("name").(string), MTU: d.Get("mtu").(int), - AcceptRA: d.Get("accept_ra").(bool), - Autoconf: d.Get("autoconf").(bool), - } - - if p, ok := d.GetOk("name"); ok { - params.Name = p.(string) + Tags: strings.Join(convertToStringSlice(d.Get("tags").(*schema.Set).List()), ","), } - if p, ok := d.GetOk("tags"); ok { - params.Tags = strings.Join(convertToStringSlice(p.(*schema.Set).List()), ",") - } - - return ¶ms } -func findNetworkInterfacePhysical(client *client.Client, machineId string, identifier string) (*entity.NetworkInterface, error) { - networkInterfaces, err := client.NetworkInterfaces.Get(machineId) +func findNetworkInterfacePhysical(client *client.Client, machineSystemID string, identifier string) (*entity.NetworkInterface, error) { + networkInterfaces, err := client.NetworkInterfaces.Get(machineSystemID) if err != nil { return nil, err } - for _, n := range networkInterfaces { if n.Type != "physical" { continue @@ -221,6 +192,16 @@ func findNetworkInterfacePhysical(client *client.Client, machineId string, ident return &n, nil } } - return nil, nil } + +func getNetworkInterfacePhysical(client *client.Client, machineSystemID string, identifier string) (*entity.NetworkInterface, error) { + n, err := findNetworkInterfacePhysical(client, machineSystemID, identifier) + if err != nil { + return nil, err + } + if n != nil { + return n, nil + } + return nil, fmt.Errorf("physical network interface (%s) was not found on machine (%s)", identifier, machineSystemID) +} diff --git a/maas/resource_maas_space.go b/maas/resource_maas_space.go index 5477b31b..24f53d4f 100644 --- a/maas/resource_maas_space.go +++ b/maas/resource_maas_space.go @@ -24,10 +24,13 @@ func resourceMaasSpace() *schema.Resource { if err != nil { return nil, err } - if err := d.Set("name", space.Name); err != nil { + tfState := map[string]interface{}{ + "id": fmt.Sprintf("%v", space.ID), + "name": space.Name, + } + if err := setTerraformState(d, tfState); err != nil { return nil, err } - d.SetId(fmt.Sprintf("%v", space.ID)) return []*schema.ResourceData{d}, nil }, }, @@ -60,8 +63,7 @@ func resourceSpaceRead(ctx context.Context, d *schema.ResourceData, m interface{ if err != nil { return diag.FromErr(err) } - _, err = client.Space.Get(id) - if err != nil { + if _, err := client.Space.Get(id); err != nil { return diag.FromErr(err) } @@ -75,8 +77,7 @@ func resourceSpaceUpdate(ctx context.Context, d *schema.ResourceData, m interfac if err != nil { return diag.FromErr(err) } - _, err = client.Space.Update(id, d.Get("name").(string)) - if err != nil { + if _, err := client.Space.Update(id, d.Get("name").(string)); err != nil { return diag.FromErr(err) } @@ -90,8 +91,7 @@ func resourceSpaceDelete(ctx context.Context, d *schema.ResourceData, m interfac if err != nil { return diag.FromErr(err) } - err = client.Space.Delete(id) - if err != nil { + if err := client.Space.Delete(id); err != nil { return diag.FromErr(err) } diff --git a/maas/resource_maas_subnet.go b/maas/resource_maas_subnet.go index 3b5d0da1..f6386447 100644 --- a/maas/resource_maas_subnet.go +++ b/maas/resource_maas_subnet.go @@ -5,8 +5,6 @@ import ( "fmt" "strconv" - "github.com/hashicorp/go-cty/cty" - "github.com/hashicorp/go-cty/cty/gocty" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" @@ -27,28 +25,20 @@ func resourceMaasSubnet() *schema.Resource { if err != nil { return nil, err } - dnsServers := make([]string, len(subnet.DNSServers)) - for i, ip := range subnet.DNSServers { - dnsServers[i] = ip.String() - } tfState := map[string]interface{}{ + "id": fmt.Sprintf("%v", subnet.ID), "cidr": subnet.CIDR, "name": subnet.Name, "fabric": fmt.Sprintf("%v", subnet.VLAN.FabricID), "vlan": fmt.Sprintf("%v", subnet.VLAN.VID), - "gateway_ip": subnet.GatewayIP.String(), - "dns_servers": dnsServers, "rdns_mode": subnet.RDNSMode, "allow_dns": subnet.AllowDNS, "allow_proxy": subnet.AllowProxy, "managed": subnet.Managed, } - for k, v := range tfState { - if err := d.Set(k, v); err != nil { - return nil, err - } + if err := setTerraformState(d, tfState); err != nil { + return nil, err } - d.SetId(fmt.Sprintf("%v", subnet.ID)) return []*schema.ResourceData{d}, nil }, }, @@ -71,44 +61,6 @@ func resourceMaasSubnet() *schema.Resource { Optional: true, RequiredWith: []string{"fabric"}, }, - "gateway_ip": { - Type: schema.TypeString, - Optional: true, - ValidateDiagFunc: validation.ToDiagFunc(validation.IsIPAddress), - }, - "dns_servers": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - ValidateDiagFunc: func(i interface{}, p cty.Path) diag.Diagnostics { - var diags diag.Diagnostics - - attr := p[len(p)-1].(cty.IndexStep) - var index int - if err := gocty.FromCtyValue(attr.Key, &index); err != nil { - return diag.FromErr(err) - } - ws, es := validation.IsIPAddress(i, fmt.Sprintf("dns_servers[%v]", index)) - - for _, w := range ws { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Warning, - Summary: w, - AttributePath: p, - }) - } - for _, e := range es { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: e.Error(), - AttributePath: p, - }) - } - return diags - }, - Type: schema.TypeString, - }, - }, "ip_ranges": { Type: schema.TypeSet, Optional: true, @@ -137,8 +89,10 @@ func resourceMaasSubnet() *schema.Resource { }, }, "rdns_mode": { - Type: schema.TypeInt, - Optional: true, + Type: schema.TypeInt, + Optional: true, + Default: 2, + ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(0, 2)), }, "allow_dns": { Type: schema.TypeBool, @@ -155,6 +109,21 @@ func resourceMaasSubnet() *schema.Resource { Optional: true, Default: true, }, + "gateway_ip": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.IsIPAddress), + }, + "dns_servers": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + ValidateDiagFunc: isElementIPAddress, + Type: schema.TypeString, + }, + }, }, } } @@ -182,10 +151,25 @@ func resourceSubnetRead(ctx context.Context, d *schema.ResourceData, m interface if err != nil { return diag.FromErr(err) } - _, err = client.Subnet.Get(id) + subnet, err := client.Subnet.Get(id) if err != nil { return diag.FromErr(err) } + gatewayIp := subnet.GatewayIP.String() + if gatewayIp == "" { + gatewayIp = "" + } + dnsServers := make([]string, len(subnet.DNSServers)) + for i, ip := range subnet.DNSServers { + dnsServers[i] = ip.String() + } + tfState := map[string]interface{}{ + "gateway_ip": gatewayIp, + "dns_servers": dnsServers, + } + if err := setTerraformState(d, tfState); err != nil { + return diag.FromErr(err) + } return nil } @@ -201,8 +185,7 @@ func resourceSubnetUpdate(ctx context.Context, d *schema.ResourceData, m interfa if err != nil { return diag.FromErr(err) } - _, err = client.Subnet.Update(id, params) - if err != nil { + if _, err := client.Subnet.Update(id, params); err != nil { return diag.FromErr(err) } if err := updateIPRanges(client, d, id); err != nil { @@ -219,8 +202,7 @@ func resourceSubnetDelete(ctx context.Context, d *schema.ResourceData, m interfa if err != nil { return diag.FromErr(err) } - err = client.Subnet.Delete(id) - if err != nil { + if err := client.Subnet.Delete(id); err != nil { return diag.FromErr(err) } @@ -255,8 +237,7 @@ func updateIPRanges(client *client.Client, d *schema.ResourceData, subnetID int) EndIP: ipr["end_ip"].(string), Comment: ipr["comment"].(string), } - _, err := client.IPRanges.Create(¶ms) - if err != nil { + if _, err := client.IPRanges.Create(¶ms); err != nil { return err } } @@ -266,12 +247,13 @@ func updateIPRanges(client *client.Client, d *schema.ResourceData, subnetID int) func getSubnetParams(client *client.Client, d *schema.ResourceData) (*entity.SubnetParams, error) { params := entity.SubnetParams{ CIDR: d.Get("cidr").(string), + Name: d.Get("name").(string), + RDNSMode: d.Get("rdns_mode").(int), AllowDNS: d.Get("allow_dns").(bool), AllowProxy: d.Get("allow_proxy").(bool), Managed: d.Get("managed").(bool), - } - if p, ok := d.GetOk("name"); ok { - params.Name = p.(string) + GatewayIP: d.Get("gateway_ip").(string), + DNSServers: convertToStringSlice(d.Get("dns_servers")), } if p, ok := d.GetOk("fabric"); ok { fabric, err := getFabric(client, p.(string)) @@ -288,15 +270,6 @@ func getSubnetParams(client *client.Client, d *schema.ResourceData) (*entity.Sub params.VID = vlan.VID } } - if p, ok := d.GetOk("gateway_ip"); ok { - params.GatewayIP = p.(string) - } - if p, ok := d.GetOk("dns_servers"); ok { - params.DNSServers = convertToStringSlice(p) - } - if p, ok := d.GetOk("rdns_mode"); ok { - params.RDNSMode = p.(int) - } return ¶ms, nil } @@ -306,7 +279,7 @@ func findSubnet(client *client.Client, identifier string) (*entity.Subnet, error return nil, err } for _, s := range subnets { - if fmt.Sprintf("%v", s.ID) == identifier || s.CIDR == identifier || s.Name == identifier { + if fmt.Sprintf("%v", s.ID) == identifier || s.CIDR == identifier { return &s, nil } } diff --git a/maas/resource_maas_subnet_ip_range.go b/maas/resource_maas_subnet_ip_range.go index 025510a5..25d8365e 100644 --- a/maas/resource_maas_subnet_ip_range.go +++ b/maas/resource_maas_subnet_ip_range.go @@ -44,18 +44,15 @@ func resourceMaasSubnetIPRange() *schema.Resource { } } tfState := map[string]interface{}{ - "subnet": ipRange.Subnet.ID, + "id": fmt.Sprintf("%v", ipRange.ID), + "subnet": fmt.Sprintf("%v", ipRange.Subnet.ID), "type": ipRange.Type, "start_ip": ipRange.StartIP.String(), "end_ip": ipRange.EndIP.String(), - "comment": ipRange.Comment, } - for k, v := range tfState { - if err := d.Set(k, v); err != nil { - return nil, err - } + if err := setTerraformState(d, tfState); err != nil { + return nil, err } - d.SetId(fmt.Sprintf("%v", ipRange.ID)) return []*schema.ResourceData{d}, nil }, }, @@ -83,6 +80,7 @@ func resourceMaasSubnetIPRange() *schema.Resource { "comment": { Type: schema.TypeString, Optional: true, + Computed: true, }, }, } @@ -111,10 +109,16 @@ func resourceSubnetIPRangeRead(ctx context.Context, d *schema.ResourceData, m in if err != nil { return diag.FromErr(err) } - _, err = client.IPRange.Get(id) + ipRange, err := client.IPRange.Get(id) if err != nil { return diag.FromErr(err) } + tfState := map[string]interface{}{ + "comment": ipRange.Comment, + } + if err := setTerraformState(d, tfState); err != nil { + return diag.FromErr(err) + } return nil } diff --git a/maas/resource_maas_tag.go b/maas/resource_maas_tag.go index c971cf4d..197838de 100644 --- a/maas/resource_maas_tag.go +++ b/maas/resource_maas_tag.go @@ -2,6 +2,7 @@ package maas import ( "context" + "fmt" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -17,7 +18,25 @@ func resourceMaasTag() *schema.Resource { DeleteContext: resourceTagDelete, Importer: &schema.ResourceImporter{ StateContext: func(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { - if err := d.Set("name", d.Id()); err != nil { + client := m.(*client.Client) + tag, err := getTag(client, d.Id()) + if err != nil { + return nil, err + } + machines, err := client.Tag.GetMachines(tag.Name) + if err != nil { + return nil, err + } + machinesSystemIDs := make([]string, len(machines)) + for i, machine := range machines { + machinesSystemIDs[i] = machine.SystemID + } + tfState := map[string]interface{}{ + "id": tag.Name, + "name": tag.Name, + "machines": machinesSystemIDs, + } + if err := setTerraformState(d, tfState); err != nil { return nil, err } return []*schema.ResourceData{d}, nil @@ -30,7 +49,7 @@ func resourceMaasTag() *schema.Resource { Required: true, ForceNew: true, }, - "machine_ids": { + "machines": { Type: schema.TypeSet, Optional: true, Elem: &schema.Schema{ @@ -55,7 +74,6 @@ func resourceTagCreate(ctx context.Context, d *schema.ResourceData, m interface{ return diag.FromErr(err) } } - d.SetId(tag.Name) return resourceTagUpdate(ctx, d, m) @@ -64,8 +82,7 @@ func resourceTagCreate(ctx context.Context, d *schema.ResourceData, m interface{ func resourceTagRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*client.Client) - _, err := client.Tag.Get(d.Id()) - if err != nil { + if _, err := client.Tag.Get(d.Id()); err != nil { return diag.FromErr(err) } @@ -75,15 +92,18 @@ func resourceTagRead(ctx context.Context, d *schema.ResourceData, m interface{}) func resourceTagUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*client.Client) - if p, ok := d.GetOk("machine_ids"); ok { - machineIds := convertToStringSlice(p.(*schema.Set).List()) + tagMachinesIDs, err := getTagTFMachinesSystemIDs(client, d) + if err != nil { + return diag.FromErr(err) + } + if len(tagMachinesIDs) > 0 { // Tag specified machines - err := client.Tag.AddMachines(d.Id(), machineIds) + err := client.Tag.AddMachines(d.Id(), tagMachinesIDs) if err != nil { return diag.FromErr(err) } // Untag previously tagged machines - err = untagOtherMachines(client, d.Id(), machineIds) + err = untagOtherMachines(client, d.Id(), tagMachinesIDs) if err != nil { return diag.FromErr(err) } @@ -95,8 +115,7 @@ func resourceTagUpdate(ctx context.Context, d *schema.ResourceData, m interface{ func resourceTagDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*client.Client) - err := client.Tag.Delete(d.Id()) - if err != nil { + if err := client.Tag.Delete(d.Id()); err != nil { return diag.FromErr(err) } @@ -122,16 +141,42 @@ func findTag(client *client.Client, tagName string) (*entity.Tag, error) { return nil, nil } -func untagOtherMachines(client *client.Client, tagName string, taggedMachineIds []string) error { +func getTag(client *client.Client, tagName string) (*entity.Tag, error) { + tag, err := findTag(client, tagName) + if err != nil { + return nil, err + } + if tag == nil { + return nil, fmt.Errorf("tag (%s) was not found", tagName) + } + return tag, nil +} + +func getTagTFMachinesSystemIDs(client *client.Client, d *schema.ResourceData) ([]string, error) { + p, ok := d.GetOk("machines") + if !ok { + return nil, nil + } + machinesSystemIDs := []string{} + for _, machineIdentifier := range convertToStringSlice(p.(*schema.Set).List()) { + machine, err := getMachine(client, machineIdentifier) + if err != nil { + return nil, err + } + machinesSystemIDs = append(machinesSystemIDs, machine.SystemID) + } + return machinesSystemIDs, nil +} + +func untagOtherMachines(client *client.Client, tagName string, taggedMachineIDs []string) error { machines, err := client.Tag.GetMachines(tagName) if err != nil { return err } - otherMachines := []string{} for _, m := range machines { found := false - for _, id := range taggedMachineIds { + for _, id := range taggedMachineIDs { if m.SystemID == id { found = true break @@ -142,10 +187,8 @@ func untagOtherMachines(client *client.Client, tagName string, taggedMachineIds } otherMachines = append(otherMachines, m.SystemID) } - if len(otherMachines) > 0 { client.Tag.RemoveMachines(tagName, otherMachines) } - return nil } diff --git a/maas/resource_maas_vlan.go b/maas/resource_maas_vlan.go index f59846f0..493ae4d8 100644 --- a/maas/resource_maas_vlan.go +++ b/maas/resource_maas_vlan.go @@ -3,7 +3,6 @@ package maas import ( "context" "fmt" - "strconv" "strings" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -33,22 +32,14 @@ func resourceMaasVlan() *schema.Resource { if err != nil { return nil, err } - if err := d.Set("fabric", fmt.Sprintf("%v", fabric.ID)); err != nil { - return nil, err - } - if err := d.Set("vid", vlan.VID); err != nil { - return nil, err + tfState := map[string]interface{}{ + "id": fmt.Sprintf("%v", vlan.ID), + "fabric": fmt.Sprintf("%v", fabric.ID), + "vid": vlan.VID, } - if err := d.Set("name", vlan.Name); err != nil { + if err := setTerraformState(d, tfState); err != nil { return nil, err } - if err := d.Set("mtu", vlan.MTU); err != nil { - return nil, err - } - if err := d.Set("space", vlan.Space); err != nil { - return nil, err - } - d.SetId(fmt.Sprintf("%v", vlan.VID)) return []*schema.ResourceData{d}, nil }, }, @@ -67,7 +58,12 @@ func resourceMaasVlan() *schema.Resource { "mtu": { Type: schema.TypeInt, Optional: true, - Default: 1500, + Computed: true, + }, + "dhcp_on": { + Type: schema.TypeBool, + Optional: true, + Computed: true, }, "name": { Type: schema.TypeString, @@ -94,7 +90,7 @@ func resourceVlanCreate(ctx context.Context, d *schema.ResourceData, m interface if err != nil { return diag.FromErr(err) } - d.SetId(fmt.Sprintf("%v", vlan.VID)) + d.SetId(fmt.Sprintf("%v", vlan.ID)) return resourceVlanUpdate(ctx, d, m) } @@ -106,10 +102,19 @@ func resourceVlanRead(ctx context.Context, d *schema.ResourceData, m interface{} if err != nil { return diag.FromErr(err) } - _, err = getVlan(client, fabric.ID, d.Id()) + vlan, err := getVlan(client, fabric.ID, d.Id()) if err != nil { return diag.FromErr(err) } + tfState := map[string]interface{}{ + "mtu": vlan.MTU, + "dhcp_on": vlan.DHCPOn, + "name": vlan.Name, + "space": vlan.Space, + } + if err := setTerraformState(d, tfState); err != nil { + return diag.FromErr(err) + } return nil } @@ -117,16 +122,15 @@ func resourceVlanRead(ctx context.Context, d *schema.ResourceData, m interface{} func resourceVlanUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*client.Client) - id, err := strconv.Atoi(d.Id()) + fabric, err := getFabric(client, d.Get("fabric").(string)) if err != nil { return diag.FromErr(err) } - fabric, err := getFabric(client, d.Get("fabric").(string)) + vlan, err := getVlan(client, fabric.ID, d.Id()) if err != nil { return diag.FromErr(err) } - _, err = client.VLAN.Update(fabric.ID, id, getVlanParams(d)) - if err != nil { + if _, err := client.VLAN.Update(fabric.ID, vlan.VID, getVlanParams(d)); err != nil { return diag.FromErr(err) } @@ -136,16 +140,15 @@ func resourceVlanUpdate(ctx context.Context, d *schema.ResourceData, m interface func resourceVlanDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*client.Client) - id, err := strconv.Atoi(d.Id()) + fabric, err := getFabric(client, d.Get("fabric").(string)) if err != nil { return diag.FromErr(err) } - fabric, err := getFabric(client, d.Get("fabric").(string)) + vlan, err := getVlan(client, fabric.ID, d.Id()) if err != nil { return diag.FromErr(err) } - err = client.VLAN.Delete(fabric.ID, id) - if err != nil { + if err := client.VLAN.Delete(fabric.ID, vlan.VID); err != nil { return diag.FromErr(err) } @@ -153,17 +156,13 @@ func resourceVlanDelete(ctx context.Context, d *schema.ResourceData, m interface } func getVlanParams(d *schema.ResourceData) *entity.VLANParams { - params := entity.VLANParams{ - VID: d.Get("vid").(int), - MTU: d.Get("mtu").(int), - } - if p, ok := d.GetOk("name"); ok { - params.Name = p.(string) - } - if p, ok := d.GetOk("space"); ok { - params.Space = p.(string) + return &entity.VLANParams{ + VID: d.Get("vid").(int), + MTU: d.Get("mtu").(int), + DHCPOn: d.Get("dhcp_on").(bool), + Name: d.Get("name").(string), + Space: d.Get("space").(string), } - return ¶ms } func findVlan(client *client.Client, fabricID int, identifier string) (*entity.VLAN, error) { @@ -172,7 +171,7 @@ func findVlan(client *client.Client, fabricID int, identifier string) (*entity.V return nil, err } for _, v := range vlans { - if fmt.Sprintf("%v", v.VID) == identifier || fmt.Sprintf("%v", v.ID) == identifier || fmt.Sprintf("%v", v.Name) == identifier { + if fmt.Sprintf("%v", v.VID) == identifier || fmt.Sprintf("%v", v.ID) == identifier { return &v, nil } } diff --git a/maas/resource_maas_vm_host.go b/maas/resource_maas_vm_host.go index 6229f759..b29738e1 100644 --- a/maas/resource_maas_vm_host.go +++ b/maas/resource_maas_vm_host.go @@ -18,8 +18,6 @@ var ( "machine", "power_address", } - defaultCPUOverCommitRatio = 1.0 - defaultMemoryOverCommitRatio = 1.0 ) func resourceMaasVMHost() *schema.Resource { @@ -31,50 +29,40 @@ func resourceMaasVMHost() *schema.Resource { Importer: &schema.ResourceImporter{ StateContext: func(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { client := m.(*client.Client) - vmHost, err := findVMHost(client, d.Id()) + vmHost, err := getVMHost(client, d.Id()) if err != nil { return nil, err } - d.SetId(fmt.Sprintf("%v", vmHost.ID)) - if err := d.Set("type", vmHost.Type); err != nil { - return nil, err - } - if err := d.Set("cpu_over_commit_ratio", defaultCPUOverCommitRatio); err != nil { - return nil, err - } - if err := d.Set("memory_over_commit_ratio", defaultMemoryOverCommitRatio); err != nil { - return nil, err + tfState := map[string]interface{}{ + "id": fmt.Sprintf("%v", vmHost.ID), + "type": vmHost.Type, } if vmHost.Host.SystemID != "" { - if err := d.Set("machine", vmHost.Host.SystemID); err != nil { - return nil, err - } + tfState["machine"] = vmHost.Host.SystemID } else { vmHostParams, err := client.VMHost.GetParameters(vmHost.ID) if err != nil { return nil, err } - if err := d.Set("power_address", vmHostParams.PowerAddress); err != nil { - return nil, err - } - if err := d.Set("power_user", vmHostParams.PowerUser); err != nil { - return nil, err - } - if err := d.Set("power_pass", vmHostParams.PowerPass); err != nil { - return nil, err + for _, k := range []string{"power_address", "power_user", "power_pass"} { + if val, ok := vmHostParams[k]; ok { + tfState[k] = val + } } } + if err := setTerraformState(d, tfState); err != nil { + return nil, err + } return []*schema.ResourceData{d}, nil }, }, Schema: map[string]*schema.Schema{ "type": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateDiagFunc: validation.ToDiagFunc( - validation.StringInSlice([]string{"lxd", "virsh"}, false)), + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"lxd", "virsh"}, false)), }, "machine": { Type: schema.TypeString, @@ -126,30 +114,18 @@ func resourceMaasVMHost() *schema.Resource { "cpu_over_commit_ratio": { Type: schema.TypeFloat, Optional: true, - Default: defaultCPUOverCommitRatio, + Computed: true, }, "memory_over_commit_ratio": { Type: schema.TypeFloat, Optional: true, - Default: defaultMemoryOverCommitRatio, + Computed: true, }, "default_macvlan_mode": { Type: schema.TypeString, Optional: true, Computed: true, }, - "resources_cores_available": { - Type: schema.TypeInt, - Computed: true, - }, - "resources_memory_available": { - Type: schema.TypeInt, - Computed: true, - }, - "resources_local_storage_available": { - Type: schema.TypeInt, - Computed: true, - }, "resources_cores_total": { Type: schema.TypeInt, Computed: true, @@ -179,7 +155,7 @@ func resourceVMHostCreate(ctx context.Context, d *schema.ResourceData, m interfa return diag.FromErr(err) } } else { - vmHost, err = client.VMHosts.Create(getVMHostCreateParams(d)) + vmHost, err = client.VMHosts.Create(getVMHostParams(d)) if err != nil { return diag.FromErr(err) } @@ -206,37 +182,19 @@ func resourceVMHostRead(ctx context.Context, d *schema.ResourceData, m interface } // Set Terraform state - if err := d.Set("name", vmHost.Name); err != nil { - return diag.FromErr(err) - } - if err := d.Set("zone", vmHost.Zone.Name); err != nil { - return diag.FromErr(err) - } - if err := d.Set("pool", vmHost.Pool.Name); err != nil { - return diag.FromErr(err) - } - if err := d.Set("tags", vmHost.Tags); err != nil { - return diag.FromErr(err) - } - if err := d.Set("default_macvlan_mode", vmHost.DefaultMACVLANMode); err != nil { - return diag.FromErr(err) - } - if err := d.Set("resources_cores_available", vmHost.Available.Cores); err != nil { - return diag.FromErr(err) - } - if err := d.Set("resources_cores_total", vmHost.Total.Cores); err != nil { - return diag.FromErr(err) - } - if err := d.Set("resources_memory_available", vmHost.Available.Memory); err != nil { - return diag.FromErr(err) - } - if err := d.Set("resources_memory_total", vmHost.Total.Memory); err != nil { - return diag.FromErr(err) - } - if err := d.Set("resources_local_storage_available", vmHost.Available.LocalStorage); err != nil { - return diag.FromErr(err) - } - if err := d.Set("resources_local_storage_total", vmHost.Total.LocalStorage); err != nil { + tfState := map[string]interface{}{ + "name": vmHost.Name, + "zone": vmHost.Zone.Name, + "pool": vmHost.Pool.Name, + "tags": vmHost.Tags, + "cpu_over_commit_ratio": vmHost.CPUOverCommitRatio, + "memory_over_commit_ratio": vmHost.MemoryOverCommitRatio, + "default_macvlan_mode": vmHost.DefaultMACVLANMode, + "resources_cores_total": vmHost.Total.Cores, + "resources_memory_total": vmHost.Total.Memory, + "resources_local_storage_total": vmHost.Total.LocalStorage, + } + if err := setTerraformState(d, tfState); err != nil { return diag.FromErr(err) } @@ -257,11 +215,7 @@ func resourceVMHostUpdate(ctx context.Context, d *schema.ResourceData, m interfa } // Update VM host options - vmHostParams, err := client.VMHost.GetParameters(vmHost.ID) - if err != nil { - return diag.FromErr(err) - } - _, err = client.VMHost.Update(vmHost.ID, getVMHostUpdateParams(d, vmHost, vmHostParams)) + _, err = client.VMHost.Update(vmHost.ID, getVMHostParams(d)) if err != nil { return diag.FromErr(err) } @@ -303,64 +257,25 @@ func resourceVMHostDelete(ctx context.Context, d *schema.ResourceData, m interfa return nil } -func getVMHostCreateParams(d *schema.ResourceData) *entity.VMHostParams { - params := entity.VMHostParams{ +func getVMHostParams(d *schema.ResourceData) *entity.VMHostParams { + return &entity.VMHostParams{ + Name: d.Get("name").(string), Type: d.Get("type").(string), + PowerAddress: d.Get("power_address").(string), + PowerUser: d.Get("power_user").(string), + PowerPass: d.Get("power_pass").(string), CPUOverCommitRatio: d.Get("cpu_over_commit_ratio").(float64), MemoryOverCommitRatio: d.Get("memory_over_commit_ratio").(float64), + DefaultMacvlanMode: d.Get("default_macvlan_mode").(string), + Zone: d.Get("zone").(string), + Pool: d.Get("pool").(string), + Tags: strings.Join(convertToStringSlice(d.Get("tags").(*schema.Set).List()), ","), } - - if p, ok := d.GetOk("power_address"); ok { - params.PowerAddress = p.(string) - } - if p, ok := d.GetOk("power_user"); ok { - params.PowerUser = p.(string) - } - if p, ok := d.GetOk("power_pass"); ok { - params.PowerPass = p.(string) - } - - return ¶ms -} - -func getVMHostUpdateParams(d *schema.ResourceData, vmHost *entity.VMHost, params *entity.VMHostParams) *entity.VMHostParams { - params.Type = vmHost.Type - params.Name = vmHost.Name - params.CPUOverCommitRatio = d.Get("cpu_over_commit_ratio").(float64) - params.MemoryOverCommitRatio = d.Get("memory_over_commit_ratio").(float64) - params.DefaultMacvlanMode = vmHost.DefaultMACVLANMode - params.Zone = vmHost.Zone.Name - params.Pool = vmHost.Pool.Name - params.Tags = strings.Join(vmHost.Tags, ",") - - if p, ok := d.GetOk("power_address"); ok { - params.PowerAddress = p.(string) - } - if p, ok := d.GetOk("power_pass"); ok { - params.PowerPass = p.(string) - } - if p, ok := d.GetOk("name"); ok { - params.Name = p.(string) - } - if p, ok := d.GetOk("zone"); ok { - params.Zone = p.(string) - } - if p, ok := d.GetOk("pool"); ok { - params.Pool = p.(string) - } - if p, ok := d.GetOk("tags"); ok { - params.Tags = strings.Join(convertToStringSlice(p.(*schema.Set).List()), ",") - } - if p, ok := d.GetOk("default_macvlan_mode"); ok { - params.DefaultMacvlanMode = p.(string) - } - - return params } func deployMachineAsVMHost(ctx context.Context, client *client.Client, machineIdentifier string, vmHostType string) (*entity.VMHost, error) { // Find machine - machine, err := findMachine(client, machineIdentifier) + machine, err := getMachine(client, machineIdentifier) if err != nil { return nil, err } @@ -402,3 +317,16 @@ func deployMachineAsVMHost(ctx context.Context, client *client.Client, machineId return nil, fmt.Errorf("cannot find registered VM host on machine '%s'", machineIdentifier) } + +func getVMHost(client *client.Client, identifier string) (*entity.VMHost, error) { + vmHosts, err := client.VMHosts.Get() + if err != nil { + return nil, err + } + for _, vmHost := range vmHosts { + if fmt.Sprintf("%v", vmHost.ID) == identifier || vmHost.Name == identifier { + return &vmHost, err + } + } + return nil, fmt.Errorf("VM host (%s) not found", identifier) +} diff --git a/maas/resource_maas_vm_host_machine.go b/maas/resource_maas_vm_host_machine.go index 00138ec2..51a0d5bb 100644 --- a/maas/resource_maas_vm_host_machine.go +++ b/maas/resource_maas_vm_host_machine.go @@ -20,21 +20,20 @@ func resourceMaasVMHostMachine() *schema.Resource { Importer: &schema.ResourceImporter{ StateContext: func(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { client := m.(*client.Client) - machine, err := findMachine(client, d.Id()) + machine, err := getMachine(client, d.Id()) if err != nil { return nil, err } if machine.VMHost.ID == 0 || machine.VMHost.Name == "" || machine.VMHost.ResourceURI == "" { return nil, fmt.Errorf("machine (%s) is not a VM host machine", d.Id()) } - d.SetId(machine.SystemID) - if err := d.Set("vm_host", fmt.Sprintf("%v", machine.VMHost.ID)); err != nil { - return nil, err - } - if err := d.Set("cores", machine.CPUCount); err != nil { - return nil, err + tfState := map[string]interface{}{ + "id": machine.SystemID, + "vm_host": fmt.Sprintf("%v", machine.VMHost.ID), + "cores": machine.CPUCount, + "memory": machine.Memory, } - if err := d.Set("memory", machine.Memory); err != nil { + if err := setTerraformState(d, tfState); err != nil { return nil, err } return []*schema.ResourceData{d}, nil @@ -51,7 +50,6 @@ func resourceMaasVMHostMachine() *schema.Resource { Type: schema.TypeInt, Optional: true, ForceNew: true, - Default: 1, }, "pinned_cores": { Type: schema.TypeInt, @@ -62,7 +60,6 @@ func resourceMaasVMHostMachine() *schema.Resource { Type: schema.TypeInt, Optional: true, ForceNew: true, - Default: 2048, }, "network_interfaces": { Type: schema.TypeList, @@ -138,13 +135,13 @@ func resourceVMHostMachineCreate(ctx context.Context, d *schema.ResourceData, m client := m.(*client.Client) // Find VM host - vmHost, err := findVMHost(client, d.Get("vm_host").(string)) + vmHost, err := getVMHost(client, d.Get("vm_host").(string)) if err != nil { return diag.FromErr(err) } // Create VM host machine - params, err := getVMHostMachineCreateParams(d) + params, err := getVMHostMachineParams(d) if err != nil { return diag.FromErr(err) } @@ -176,16 +173,13 @@ func resourceVMHostMachineRead(ctx context.Context, d *schema.ResourceData, m in } // Set Terraform state - if err := d.Set("hostname", machine.Hostname); err != nil { - return diag.FromErr(err) + tfState := map[string]interface{}{ + "hostname": machine.Hostname, + "domain": machine.Domain.Name, + "zone": machine.Zone.Name, + "pool": machine.Pool.Name, } - if err := d.Set("domain", machine.Domain.Name); err != nil { - return diag.FromErr(err) - } - if err := d.Set("zone", machine.Zone.Name); err != nil { - return diag.FromErr(err) - } - if err := d.Set("pool", machine.Pool.Name); err != nil { + if err := setTerraformState(d, tfState); err != nil { return diag.FromErr(err) } @@ -196,12 +190,7 @@ func resourceVMHostMachineUpdate(ctx context.Context, d *schema.ResourceData, m client := m.(*client.Client) // Update VM host machine - machine, err := client.Machine.Get(d.Id()) - if err != nil { - return diag.FromErr(err) - } - _, err = client.Machine.Update(machine.SystemID, getVMHostMachineUpdateParams(d, machine), map[string]string{}) - if err != nil { + if _, err := client.Machine.Update(d.Id(), getVMHostMachineUpdateParams(d), map[string]string{}); err != nil { return diag.FromErr(err) } @@ -220,75 +209,29 @@ func resourceVMHostMachineDelete(ctx context.Context, d *schema.ResourceData, m return nil } -func findVMHost(client *client.Client, vmHostIdentifier string) (*entity.VMHost, error) { - vmHosts, err := client.VMHosts.Get() +func getVMHostMachineParams(d *schema.ResourceData) (*entity.VMHostMachineParams, error) { + networkInterfaces, err := getVMHostMachineNetworkInterfaces(d.Get("network_interfaces").([]interface{})) if err != nil { return nil, err } - - for _, vmHost := range vmHosts { - if fmt.Sprintf("%v", vmHost.ID) == vmHostIdentifier || vmHost.Name == vmHostIdentifier { - return &vmHost, err - } + params := entity.VMHostMachineParams{ + Hostname: d.Get("hostname").(string), + Cores: d.Get("cores").(int), + PinnedCores: d.Get("pinned_cores").(int), + Memory: d.Get("memory").(int), + Interfaces: networkInterfaces, + Storage: getVMHostMachineStorageDisks(d.Get("storage_disks").([]interface{})), } - - return nil, fmt.Errorf("VM host (%s) not found", vmHostIdentifier) -} - -func getVMHostMachineCreateParams(d *schema.ResourceData) (*entity.VMHostMachineParams, error) { - params := entity.VMHostMachineParams{} - - if p, ok := d.GetOk("cores"); ok { - params.Cores = p.(int) - } - if p, ok := d.GetOk("pinned_cores"); ok { - params.PinnedCores = p.(int) - } - if p, ok := d.GetOk("memory"); ok { - params.Memory = p.(int) - } - if p, ok := d.GetOk("network_interfaces"); ok { - networkInterfaces, err := getVMHostMachineNetworkInterfaces(p.([]interface{})) - if err != nil { - return nil, err - } - params.Interfaces = networkInterfaces - } - if p, ok := d.GetOk("storage_disks"); ok { - params.Storage = getVMHostMachineStorageDisks(p.([]interface{})) - } - if p, ok := d.GetOk("hostname"); ok { - params.Hostname = p.(string) - } - return ¶ms, nil } -func getVMHostMachineUpdateParams(d *schema.ResourceData, machine *entity.Machine) *entity.MachineParams { - params := entity.MachineParams{ - CPUCount: machine.CPUCount, - Memory: machine.Memory, - SwapSize: machine.SwapSize, - Architecture: machine.Architecture, - MinHWEKernel: machine.MinHWEKernel, - PowerType: machine.PowerType, - Description: machine.Description, - } - - if p, ok := d.GetOk("hostname"); ok { - params.Hostname = p.(string) +func getVMHostMachineUpdateParams(d *schema.ResourceData) *entity.MachineParams { + return &entity.MachineParams{ + Hostname: d.Get("hostname").(string), + Domain: d.Get("domain").(string), + Zone: d.Get("zone").(string), + Pool: d.Get("pool").(string), } - if p, ok := d.GetOk("domain"); ok { - params.Domain = p.(string) - } - if p, ok := d.GetOk("zone"); ok { - params.Zone = p.(string) - } - if p, ok := d.GetOk("pool"); ok { - params.Pool = p.(string) - } - - return ¶ms } func getVMHostMachineNetworkInterfaces(networkInterfaces []interface{}) (string, error) { diff --git a/maas/utils.go b/maas/utils.go index 0e0a08ba..e001907c 100644 --- a/maas/utils.go +++ b/maas/utils.go @@ -2,6 +2,15 @@ package maas import ( "encoding/base64" + "fmt" + + "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/go-cty/cty/gocty" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/ionutbalutoiu/gomaasclient/client" + "github.com/ionutbalutoiu/gomaasclient/entity" ) func base64Encode(data []byte) string { @@ -28,3 +37,56 @@ func convertToStringSlice(field interface{}) []string { } return result } + +func isElementIPAddress(i interface{}, p cty.Path) diag.Diagnostics { + var diags diag.Diagnostics + + attr := p[len(p)-1].(cty.IndexStep) + var index int + if err := gocty.FromCtyValue(attr.Key, &index); err != nil { + return diag.FromErr(err) + } + ws, es := validation.IsIPAddress(i, fmt.Sprintf("element %v", index)) + + for _, w := range ws { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Warning, + Summary: w, + AttributePath: p, + }) + } + for _, e := range es { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: e.Error(), + AttributePath: p, + }) + } + return diags +} + +func getNetworkInterface(client *client.Client, machineSystemID string, identifier string) (*entity.NetworkInterface, error) { + networkInterfaces, err := client.NetworkInterfaces.Get(machineSystemID) + if err != nil { + return nil, err + } + for _, n := range networkInterfaces { + if n.MACAddress == identifier || n.Name == identifier || fmt.Sprintf("%v", n.ID) == identifier { + return &n, nil + } + } + return nil, fmt.Errorf("network interface (%s) was not found on machine (%s)", identifier, machineSystemID) +} + +func setTerraformState(d *schema.ResourceData, tfState map[string]interface{}) error { + if val, ok := tfState["id"]; ok { + d.SetId(val.(string)) + delete(tfState, "id") + } + for k, v := range tfState { + if err := d.Set(k, v); err != nil { + return err + } + } + return nil +} From 8ff9dfb67bae58e81e0acbded3e0efc43fe508f4 Mon Sep 17 00:00:00 2001 From: Ionut Balutoiu Date: Mon, 5 Jul 2021 18:47:41 +0300 Subject: [PATCH 07/14] Bump gomaasclient --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index bfb6addd..7045bd51 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,6 @@ go 1.16 require ( github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/terraform-plugin-sdk/v2 v2.6.1 - github.com/ionutbalutoiu/gomaasclient v0.0.0-20210702152754-2e8e72fa2af2 + github.com/ionutbalutoiu/gomaasclient v0.0.0-20210706123835-65b3275559c2 github.com/stretchr/testify v1.7.0 ) diff --git a/go.sum b/go.sum index 218f6ff3..ee3e08a4 100644 --- a/go.sum +++ b/go.sum @@ -204,8 +204,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/ionutbalutoiu/gomaasclient v0.0.0-20210702152754-2e8e72fa2af2 h1:MdFWC4FH1L6hXR/UwZCKJdOpsN5CRzmJ9/ssjt3KTa8= -github.com/ionutbalutoiu/gomaasclient v0.0.0-20210702152754-2e8e72fa2af2/go.mod h1:bj39184ChgZMBH01zYHoUV2h4mSmAYisQSHs/sRzYQE= +github.com/ionutbalutoiu/gomaasclient v0.0.0-20210706123835-65b3275559c2 h1:Ed/X3MVwFMOh/cgbuGz/fO6mwYkAaj7sy95MSAKOtCU= +github.com/ionutbalutoiu/gomaasclient v0.0.0-20210706123835-65b3275559c2/go.mod h1:bj39184ChgZMBH01zYHoUV2h4mSmAYisQSHs/sRzYQE= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= From cf0fd128ec19a2f3499464ce79c48d2fba41fc90 Mon Sep 17 00:00:00 2001 From: Ionut Balutoiu Date: Mon, 5 Jul 2021 14:39:29 +0300 Subject: [PATCH 08/14] Add maas_dns_domain managed resource --- maas/provider.go | 1 + maas/resource_maas_dns_domain.go | 143 +++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 maas/resource_maas_dns_domain.go diff --git a/maas/provider.go b/maas/provider.go index b21964ac..eb771ce5 100644 --- a/maas/provider.go +++ b/maas/provider.go @@ -42,6 +42,7 @@ func Provider() *schema.Provider { "maas_vlan": resourceMaasVlan(), "maas_subnet": resourceMaasSubnet(), "maas_subnet_ip_range": resourceMaasSubnetIPRange(), + "maas_dns_domain": resourceMaasDnsDomain(), "maas_space": resourceMaasSpace(), "maas_block_device": resourceMaasBlockDevice(), "maas_tag": resourceMaasTag(), diff --git a/maas/resource_maas_dns_domain.go b/maas/resource_maas_dns_domain.go new file mode 100644 index 00000000..4e202a29 --- /dev/null +++ b/maas/resource_maas_dns_domain.go @@ -0,0 +1,143 @@ +package maas + +import ( + "context" + "fmt" + "strconv" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/ionutbalutoiu/gomaasclient/client" + "github.com/ionutbalutoiu/gomaasclient/entity" +) + +func resourceMaasDnsDomain() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceDnsDomainCreate, + ReadContext: resourceDnsDomainRead, + UpdateContext: resourceDnsDomainUpdate, + DeleteContext: resourceDnsDomainDelete, + Importer: &schema.ResourceImporter{ + StateContext: func(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + client := m.(*client.Client) + domain, err := getDomain(client, d.Id()) + if err != nil { + return nil, err + } + tfState := map[string]interface{}{ + "id": fmt.Sprintf("%v", domain.ID), + "name": domain.Name, + "ttl": domain.TTL, + "authoritative": domain.Authoritative, + "is_default": domain.IsDefault, + } + if err := setTerraformState(d, tfState); err != nil { + return nil, err + } + return []*schema.ResourceData{d}, nil + }, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "ttl": { + Type: schema.TypeInt, + Optional: true, + }, + "authoritative": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "is_default": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + }, + } +} + +func resourceDnsDomainCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + + domain, err := client.Domains.Create(getDomainParams(d)) + if err != nil { + return diag.FromErr(err) + } + d.SetId(fmt.Sprintf("%v", domain.ID)) + + return resourceDnsDomainUpdate(ctx, d, m) +} + +func resourceDnsDomainRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + + id, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + if _, err := client.Domain.Get(id); err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceDnsDomainUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + + id, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + domain, err := client.Domain.Update(id, getDomainParams(d)) + if err != nil { + return diag.FromErr(err) + } + if d.Get("is_default").(bool) { + if _, err := client.Domain.SetDefault(domain.ID); err != nil { + return diag.FromErr(err) + } + } + + return resourceDnsDomainRead(ctx, d, m) +} + +func resourceDnsDomainDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + + id, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + if err := client.Domain.Delete(id); err != nil { + return diag.FromErr(err) + } + + return nil +} + +func getDomainParams(d *schema.ResourceData) *entity.DomainParams { + return &entity.DomainParams{ + Name: d.Get("name").(string), + TTL: d.Get("ttl").(int), + Authoritative: d.Get("authoritative").(bool), + } +} + +func getDomain(client *client.Client, identifier string) (*entity.Domain, error) { + domains, err := client.Domains.Get() + if err != nil { + return nil, err + } + for _, d := range domains { + if fmt.Sprintf("%v", d.ID) == identifier || d.Name == identifier { + return &d, nil + } + } + return nil, fmt.Errorf("domain (%s) was not found", identifier) +} From 775ccbbf100e762bc248ec4e6775525fa96420db Mon Sep 17 00:00:00 2001 From: Ionut Balutoiu Date: Mon, 5 Jul 2021 18:45:44 +0300 Subject: [PATCH 09/14] Add maas_dns_record managed resource --- maas/provider.go | 1 + maas/resource_maas_dns_record.go | 246 +++++++++++++++++++++++++++++++ 2 files changed, 247 insertions(+) create mode 100644 maas/resource_maas_dns_record.go diff --git a/maas/provider.go b/maas/provider.go index eb771ce5..d1a9ad05 100644 --- a/maas/provider.go +++ b/maas/provider.go @@ -43,6 +43,7 @@ func Provider() *schema.Provider { "maas_subnet": resourceMaasSubnet(), "maas_subnet_ip_range": resourceMaasSubnetIPRange(), "maas_dns_domain": resourceMaasDnsDomain(), + "maas_dns_record": resourceMaasDnsRecord(), "maas_space": resourceMaasSpace(), "maas_block_device": resourceMaasBlockDevice(), "maas_tag": resourceMaasTag(), diff --git a/maas/resource_maas_dns_record.go b/maas/resource_maas_dns_record.go new file mode 100644 index 00000000..ef3e280b --- /dev/null +++ b/maas/resource_maas_dns_record.go @@ -0,0 +1,246 @@ +package maas + +import ( + "context" + "fmt" + "strconv" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/ionutbalutoiu/gomaasclient/client" + "github.com/ionutbalutoiu/gomaasclient/entity" +) + +var ( + validDnsRecordTypes = []string{"A/AAAA", "CNAME", "MX", "NS", "SRV", "SSHFP", "TXT"} +) + +func resourceMaasDnsRecord() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceDnsRecordCreate, + ReadContext: resourceDnsRecordRead, + UpdateContext: resourceDnsRecordUpdate, + DeleteContext: resourceDnsRecordDelete, + Importer: &schema.ResourceImporter{ + StateContext: func(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + idParts := strings.Split(d.Id(), ":") + if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { + return nil, fmt.Errorf("unexpected format of ID (%q), expected TYPE:IDENTIFIER", d.Id()) + } + resourceType := idParts[0] + if _, errors := validation.StringInSlice(validDnsRecordTypes, false)(resourceType, "type"); len(errors) > 0 { + return nil, errors[0] + } + client := m.(*client.Client) + resourceIdentifier := idParts[1] + var tfState map[string]interface{} + if resourceType == "A/AAAA" { + dnsRecord, err := getDnsResource(client, resourceIdentifier) + if err != nil { + return nil, err + } + ips := []string{} + for _, ipAddress := range dnsRecord.IPAddresses { + ips = append(ips, ipAddress.IP.String()) + } + tfState = map[string]interface{}{ + "id": fmt.Sprintf("%v", dnsRecord.ID), + "type": resourceType, + "data": strings.Join(ips, " "), + "fqdn": dnsRecord.FQDN, + "ttl": dnsRecord.AddressTTL, + } + } else { + dnsRecord, err := getDnsResourceRecord(client, resourceIdentifier) + if err != nil { + return nil, err + } + tfState = map[string]interface{}{ + "id": fmt.Sprintf("%v", dnsRecord.ID), + "type": dnsRecord.RRType, + "data": dnsRecord.RRData, + "fqdn": dnsRecord.FQDN, + "ttl": dnsRecord.TTL, + } + } + if err := setTerraformState(d, tfState); err != nil { + return nil, err + } + return []*schema.ResourceData{d}, nil + }, + }, + + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice(validDnsRecordTypes, false)), + }, + "data": { + Type: schema.TypeString, + Required: true, + }, + "name": { + Type: schema.TypeString, + Optional: true, + RequiredWith: []string{"domain"}, + ExactlyOneOf: []string{"name", "fqdn"}, + }, + "domain": { + Type: schema.TypeString, + Optional: true, + RequiredWith: []string{"name"}, + }, + "fqdn": { + Type: schema.TypeString, + Optional: true, + ExactlyOneOf: []string{"name", "fqdn"}, + }, + "ttl": { + Type: schema.TypeInt, + Optional: true, + }, + }, + } +} + +func resourceDnsRecordCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + + var resourceID int + if d.Get("type").(string) == "A/AAAA" { + dnsRecord, err := client.DNSResources.Create(getDnsResourceParams(d)) + if err != nil { + return diag.FromErr(err) + } + resourceID = dnsRecord.ID + } else { + dnsRecord, err := client.DNSResourceRecords.Create(getDnsResourceRecordParams(d)) + if err != nil { + return diag.FromErr(err) + } + resourceID = dnsRecord.ID + } + d.SetId(fmt.Sprintf("%v", resourceID)) + + return resourceDnsRecordUpdate(ctx, d, m) +} + +func resourceDnsRecordRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + + id, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + if d.Get("type").(string) == "A/AAAA" { + if _, err := client.DNSResource.Get(id); err != nil { + return diag.FromErr(err) + } + } else { + if _, err := client.DNSResourceRecord.Get(id); err != nil { + return diag.FromErr(err) + } + } + + return nil +} + +func resourceDnsRecordUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + + id, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + if d.Get("type").(string) == "A/AAAA" { + if _, err := client.DNSResource.Update(id, getDnsResourceParams(d)); err != nil { + return diag.FromErr(err) + } + } else { + if _, err := client.DNSResourceRecord.Update(id, getDnsResourceRecordParams(d)); err != nil { + return diag.FromErr(err) + } + } + + return resourceDnsRecordRead(ctx, d, m) +} + +func resourceDnsRecordDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + + id, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + if d.Get("type").(string) == "A/AAAA" { + dnsResource, err := client.DNSResource.Get(id) + if err != nil { + return diag.FromErr(err) + } + if err := client.DNSResource.Delete(id); err != nil { + return diag.FromErr(err) + } + for _, ipAddress := range dnsResource.IPAddresses { + if err := client.IPAddresses.Release(&entity.IPAddressesParams{IP: ipAddress.IP.String()}); err != nil { + return diag.FromErr(err) + } + } + } else { + if err := client.DNSResourceRecord.Delete(id); err != nil { + return diag.FromErr(err) + } + } + + return nil +} + +func getDnsResourceParams(d *schema.ResourceData) *entity.DNSResourceParams { + return &entity.DNSResourceParams{ + IPAddresses: d.Get("data").(string), + Name: d.Get("name").(string), + Domain: d.Get("domain").(string), + FQDN: d.Get("fqdn").(string), + AddressTTL: d.Get("ttl").(int), + } +} + +func getDnsResourceRecordParams(d *schema.ResourceData) *entity.DNSResourceRecordParams { + return &entity.DNSResourceRecordParams{ + RRType: d.Get("type").(string), + RRData: d.Get("data").(string), + Name: d.Get("name").(string), + Domain: d.Get("domain").(string), + FQDN: d.Get("fqdn").(string), + TTL: d.Get("ttl").(int), + } +} + +func getDnsResourceRecord(client *client.Client, identifier string) (*entity.DNSResourceRecord, error) { + dnsResourceRecords, err := client.DNSResourceRecords.Get() + if err != nil { + return nil, err + } + for _, d := range dnsResourceRecords { + if fmt.Sprintf("%v", d.ID) == identifier || d.FQDN == identifier { + return &d, nil + } + } + return nil, fmt.Errorf("DNS resource record (%s) was not found", identifier) +} + +func getDnsResource(client *client.Client, identifier string) (*entity.DNSResource, error) { + dnsResources, err := client.DNSResources.Get() + if err != nil { + return nil, err + } + for _, d := range dnsResources { + if fmt.Sprintf("%v", d.ID) == identifier || d.FQDN == identifier { + return &d, nil + } + } + return nil, fmt.Errorf("DNS resource (%s) was not found", identifier) +} From 33bc7c8809669fcca57380ce0a672f0b2db01303 Mon Sep 17 00:00:00 2001 From: Ionut Balutoiu Date: Tue, 6 Jul 2021 17:45:43 +0300 Subject: [PATCH 10/14] Add validation for maas_machine power_type --- maas/resource_maas_machine.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/maas/resource_maas_machine.go b/maas/resource_maas_machine.go index 21d1a066..15ba9d47 100644 --- a/maas/resource_maas_machine.go +++ b/maas/resource_maas_machine.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/ionutbalutoiu/gomaasclient/client" "github.com/ionutbalutoiu/gomaasclient/entity" ) @@ -48,6 +49,13 @@ func resourceMaasMachine() *schema.Resource { "power_type": { Type: schema.TypeString, Required: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice( + []string{ + "amt", "apc", "dli", "eaton", "hmc", "ipmi", "manual", "moonshot", + "mscm", "msftocs", "nova", "openbmc", "proxmox", "recs_box", "redfish", + "sm15k", "ucsm", "vmware", "webhook", "wedge", "lxd", "virsh", + }, + false)), }, "power_parameters": { Type: schema.TypeMap, From 6fbc722aafea70bdee0f25175baf51885a9ab063 Mon Sep 17 00:00:00 2001 From: Ionut Balutoiu Date: Wed, 7 Jul 2021 01:06:18 +0300 Subject: [PATCH 11/14] Remove managed attr from subnet --- maas/data_source_maas_subnet.go | 5 ----- maas/resource_maas_subnet.go | 8 +------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/maas/data_source_maas_subnet.go b/maas/data_source_maas_subnet.go index fd96a893..42e2a9c5 100644 --- a/maas/data_source_maas_subnet.go +++ b/maas/data_source_maas_subnet.go @@ -42,10 +42,6 @@ func dataSourceMaasSubnet() *schema.Resource { Type: schema.TypeBool, Computed: true, }, - "managed": { - Type: schema.TypeBool, - Computed: true, - }, "gateway_ip": { Type: schema.TypeString, Computed: true, @@ -84,7 +80,6 @@ func dataSourceSubnetRead(ctx context.Context, d *schema.ResourceData, m interfa "rdns_mode": subnet.RDNSMode, "allow_dns": subnet.AllowDNS, "allow_proxy": subnet.AllowProxy, - "managed": subnet.Managed, "gateway_ip": gatewayIp, "dns_servers": dnsServers, } diff --git a/maas/resource_maas_subnet.go b/maas/resource_maas_subnet.go index f6386447..2a5654f8 100644 --- a/maas/resource_maas_subnet.go +++ b/maas/resource_maas_subnet.go @@ -34,7 +34,6 @@ func resourceMaasSubnet() *schema.Resource { "rdns_mode": subnet.RDNSMode, "allow_dns": subnet.AllowDNS, "allow_proxy": subnet.AllowProxy, - "managed": subnet.Managed, } if err := setTerraformState(d, tfState); err != nil { return nil, err @@ -104,11 +103,6 @@ func resourceMaasSubnet() *schema.Resource { Optional: true, Default: true, }, - "managed": { - Type: schema.TypeBool, - Optional: true, - Default: true, - }, "gateway_ip": { Type: schema.TypeString, Optional: true, @@ -251,9 +245,9 @@ func getSubnetParams(client *client.Client, d *schema.ResourceData) (*entity.Sub RDNSMode: d.Get("rdns_mode").(int), AllowDNS: d.Get("allow_dns").(bool), AllowProxy: d.Get("allow_proxy").(bool), - Managed: d.Get("managed").(bool), GatewayIP: d.Get("gateway_ip").(string), DNSServers: convertToStringSlice(d.Get("dns_servers")), + Managed: true, } if p, ok := d.GetOk("fabric"); ok { fabric, err := getFabric(client, p.(string)) From e9f5d3f53a9c299b05eb94e7ab396733b188bc30 Mon Sep 17 00:00:00 2001 From: Ionut Balutoiu Date: Mon, 5 Jul 2021 19:39:26 +0300 Subject: [PATCH 12/14] Update examples Add examples for maas_domain and maas_dns_record managed resources --- examples/0-network.tf | 31 +++++++++++++++++++++++++++++++ examples/output.tf | 4 ++++ 2 files changed, 35 insertions(+) diff --git a/examples/0-network.tf b/examples/0-network.tf index 231c0f9e..d58689d1 100644 --- a/examples/0-network.tf +++ b/examples/0-network.tf @@ -92,3 +92,34 @@ resource "maas_subnet_ip_range" "reserved_ip_range" { end_ip = "10.77.77.254" comment = "Reserved for Static IPs" } + +# +# DNS Domains +# +resource "maas_dns_domain" "cloudbase" { + name = "cloudbase" + ttl = 3600 + authoritative = true +} + +# +# DNS Records +# +resource "maas_dns_record" "test_a" { + type = "A/AAAA" + data = "10.99.11.33" + fqdn = "test-a.${maas_dns_domain.cloudbase.name}" +} + +resource "maas_dns_record" "test_aaaa" { + type = "A/AAAA" + data = "2001:db8:3333:4444:5555:6666:7777:8888" + fqdn = "test-aaaa.${maas_dns_domain.cloudbase.name}" +} + +resource "maas_dns_record" "test_txt" { + type = "TXT" + data = "@" + name = "test-txt" + domain = maas_dns_domain.cloudbase.name +} diff --git a/examples/output.tf b/examples/output.tf index 4ad022dd..87b5a6f4 100644 --- a/examples/output.tf +++ b/examples/output.tf @@ -11,6 +11,10 @@ output "maas_subnet_tf_subnet" { value = maas_subnet.tf_subnet } output "maas_subnet_tf_subnet_2" { value = maas_subnet.tf_subnet_2 } output "maas_subnet_ip_range_dynamic" { value = maas_subnet_ip_range.dynamic_ip_range } output "maas_subnet_ip_range_reserved" { value = maas_subnet_ip_range.reserved_ip_range } +output "maas_dns_domain_cloudbase" { value = maas_dns_domain.cloudbase } +output "maas_dns_record_test_a" { value = maas_dns_record.test_a } +output "maas_dns_record_test_aaaa" { value = maas_dns_record.test_aaaa } +output "maas_dns_record_test_txt" { value = maas_dns_record.test_txt } # Machines output "maas_machine_1" { value = maas_machine.virsh_vm1.hostname } From 65767ceac9fdac24ad95439df929d942bffdc94b Mon Sep 17 00:00:00 2001 From: Ionut Balutoiu Date: Tue, 6 Jul 2021 13:51:01 +0300 Subject: [PATCH 13/14] Update docs --- docs/README.md | 28 +++--- docs/data_sources/maas_fabric.md | 22 +++-- docs/data_sources/maas_subnet.md | 46 +++++----- docs/data_sources/maas_vlan.md | 37 ++++---- docs/resources/maas_block_device.md | 72 ++++++++++++++++ docs/resources/maas_dns_domain.md | 37 ++++++++ docs/resources/maas_dns_record.md | 39 +++++++++ docs/resources/maas_fabric.md | 32 +++++++ docs/resources/maas_instance.md | 86 ++++++++++++++----- docs/resources/maas_machine.md | 48 +++++++---- docs/resources/maas_network_interface_link.md | 47 ++++++---- .../maas_network_interface_physical.md | 50 +++++++---- docs/resources/maas_space.md | 32 +++++++ docs/resources/maas_subnet.md | 71 +++++++++++++++ docs/resources/maas_subnet_ip_range.md | 39 +++++++++ docs/resources/maas_tag.md | 34 +++++--- docs/resources/maas_vlan.md | 40 +++++++++ docs/resources/maas_vm_host.md | 69 ++++++++++----- docs/resources/maas_vm_host_machine.md | 77 +++++++++++++---- 19 files changed, 731 insertions(+), 175 deletions(-) create mode 100644 docs/resources/maas_block_device.md create mode 100644 docs/resources/maas_dns_domain.md create mode 100644 docs/resources/maas_dns_record.md create mode 100644 docs/resources/maas_fabric.md create mode 100644 docs/resources/maas_space.md create mode 100644 docs/resources/maas_subnet.md create mode 100644 docs/resources/maas_subnet_ip_range.md create mode 100644 docs/resources/maas_vlan.md diff --git a/docs/README.md b/docs/README.md index 98e40be4..61026355 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,16 +2,24 @@ ## Data Sources -* [maas_fabric](/docs/data_sources/maas_fabric.md) -* [maas_vlan](/docs/data_sources/maas_vlan.md) -* [maas_subnet](/docs/data_sources/maas_subnet.md) +1. [maas_fabric](/docs/data_sources/maas_fabric.md) +1. [maas_vlan](/docs/data_sources/maas_vlan.md) +1. [maas_subnet](/docs/data_sources/maas_subnet.md) ## Resources -* [maas_tag](/docs/resources/maas_tag.md) -* [maas_vm_host](/docs/resources/maas_vm_host.md) -* [maas_vm_host_machine](/docs/resources/maas_vm_host_machine.md) -* [maas_machine](/docs/resources/maas_machine.md) -* [maas_network_interface_physical](/docs/resources/maas_network_interface_physical.md) -* [maas_network_interface_link](/docs/resources/maas_network_interface_link.md) -* [maas_instance](/docs/resources/maas_instance.md) +1. [maas_instance](/docs/resources/maas_instance.md) +1. [maas_vm_host](/docs/resources/maas_vm_host.md) +1. [maas_vm_host_machine](/docs/resources/maas_vm_host_machine.md) +1. [maas_machine](/docs/resources/maas_machine.md) +1. [maas_network_interface_physical](/docs/resources/maas_network_interface_physical.md) +1. [maas_network_interface_link](/docs/resources/maas_network_interface_link.md) +1. [maas_fabric](/docs/resources/maas_fabric.md) +1. [maas_vlan](/docs/resources/maas_vlan.md) +1. [maas_subnet](/docs/resources/maas_subnet.md) +1. [maas_subnet_ip_range](/docs/resources/maas_subnet_ip_range.md) +1. [maas_dns_domain](/docs/resources/maas_dns_domain.md) +1. [maas_dns_record](/docs/resources/maas_dns_record.md) +1. [maas_space](/docs/resources/maas_space.md) +1. [maas_block_device](/docs/resources/maas_block_device.md) +1. [maas_tag](/docs/resources/maas_tag.md) diff --git a/docs/data_sources/maas_fabric.md b/docs/data_sources/maas_fabric.md index 28f1cfd4..89082812 100644 --- a/docs/data_sources/maas_fabric.md +++ b/docs/data_sources/maas_fabric.md @@ -1,17 +1,23 @@ -# `maas_fabric` +# Data Source: maas_fabric -Get an existing MAAS fabric. +Provides details about an existing MAAS network fabric. -Example: +## Example Usage -```hcl +```terraform data "maas_fabric" "default" { name = "maas" } ``` -Parameters: +## Argument Reference -| Name | Type | Required | Description -| ---- | ---- | -------- | ----------- -| `name` | `string` | `true` | The fabric name. +The following arguments are supported: + +* `name` - (Required) The fabric name. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The fabric ID. diff --git a/docs/data_sources/maas_subnet.md b/docs/data_sources/maas_subnet.md index b8960f5c..cdea2b5e 100644 --- a/docs/data_sources/maas_subnet.md +++ b/docs/data_sources/maas_subnet.md @@ -1,28 +1,34 @@ -# `maas_subnet` +# Data Source: maas_subnet -Fetches details about an existing MAAS subnet. +Provides details about an existing MAAS network subnet. -Example: +## Example Usage -```hcl -data "maas_fabric" "default" { - name = "maas" +```terraform +data "maas_subnet" "vid10" { + cidr = "10.10.0.0/16" } +``` -data "maas_vlan" "default" { - fabric_id = data.maas_fabric.default.id - vid = 0 -} +## Argument Reference -data "maas_subnet" "pxe" { - cidr = "10.121.0.0/16" - vlan_id = data.maas_vlan.default.id -} -``` +The following arguments are supported: + +* `cidr` - (Requried) The subnet CIDR. + +## Attributes Reference -Parameters: +In addition to all arguments above, the following attributes are exported: -| Name | Type | Required | Description -| ---- | ---- | -------- | ----------- -| `cidr` | `string` | `true` | The network CIDR for this subnet. -| `vlan_id` | `int` | `false` | The ID of the VLAN this subnet belongs to. This is the unique identifier set by MAAS for the VLAN resource, not the actual VLAN traffic segregation ID. +* `id` - The subnet ID. +* `fabric` - The subnet fabric. +* `vid` - The subnet VLAN traffic segregation ID. +* `name` - The subnet name. +* `rdns_mode` - How reverse DNS is handled for this subnet. It can have one of the following values: + * `0` - Disabled, no reverse zone is created. + * `1` - Enabled, generate reverse zone. + * `2` - RFC2317, extends `1` to create the necessary parent zone with the appropriate CNAME resource records for the network, if the network is small enough to require the support described in RFC2317. +* `allow_dns` - Boolean value that indicates if the MAAS DNS resolution is enabled for this subnet. +* `allow_proxy` - Boolean value that indicates if `maas-proxy` allows requests from this subnet. +* `gateway_ip` - Gateway IP address for the subnet. +* `dns_servers` - List of IP addresses set as DNS servers for the subnet. diff --git a/docs/data_sources/maas_vlan.md b/docs/data_sources/maas_vlan.md index 5680d464..272b9d2f 100644 --- a/docs/data_sources/maas_vlan.md +++ b/docs/data_sources/maas_vlan.md @@ -1,23 +1,28 @@ -# `maas_vlan` +# Data Source: maas_vlan -Get an existing MAAS VLAN. +Provides details about an existing MAAS VLAN. -Example: +## Example Usage -```hcl -data "maas_fabric" "default" { - name = "maas" -} - -data "maas_vlan" "default" { - fabric_id = data.maas_fabric.default.id - vid = 0 +```terraform +data "maas_vlan" "vid10" { + fabric = data.maas_fabric.default.id + vlan = 10 } ``` -Parameters: +## Argument Reference + +The following arguments are supported: + +* `fabric` - (Required) The fabric identifier (ID or name) for the VLAN. +* `vlan` - (Required) The VLAN identifier (ID or traffic segregation ID). + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: -| Name | Type | Required | Description -| ---- | ---- | -------- | ----------- -| `fabric_id` | `int` | `true` | The ID of the fabric containing the VLAN. -| `vid` | `int` | `true` | The VLAN traffic segregation ID. +* `mtu` - The MTU used on the VLAN. +* `dhcp_on` - Boolean value indicating if DHCP is enabled on the VLAN. +* `name` - The VLAN name. +* `space` - The VLAN space. diff --git a/docs/resources/maas_block_device.md b/docs/resources/maas_block_device.md new file mode 100644 index 00000000..200b4f02 --- /dev/null +++ b/docs/resources/maas_block_device.md @@ -0,0 +1,72 @@ + +# Resource: maas_block_device + +Provides a resource to manage MAAS machines' block devices. + +## Example Usage + +```terraform +resource "maas_block_device" "vdb" { + machine = maas_machine.virsh_vm2.id + name = "vdb" + id_path = "/dev/vdb" + size_gigabytes = 27 + tags = [ + "ssd", + ] + + partitions { + size_gigabytes = 10 + fs_type = "ext4" + label = "media" + mount_point = "/media" + } + + partitions { + size_gigabytes = 15 + fs_type = "ext4" + mount_point = "/storage" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `machine` - (Required) The machine identifier (system ID, hostname, or FQDN) that owns the block device. +* `name` - (Required) The block device name. +* `size_gigabytes` - (Required) The size of the block device (given in GB). +* `block_size` - (Optional) The block size of the block device. Defaults to `512`. +* `is_boot_device` - (Optional) Boolean value indicating if the block device is set as the boot device. +* `partitions` - (Optional) List of partition resources created for the new block device. Parameters defined below. This argument is processed in [attribute-as-blocks mode](https://www.terraform.io/docs/configuration/attr-as-blocks.html). And, it is computed if it's not given. +* `model` - (Optional) Model of the block device. Used in conjunction with `serial` argument. Conflicts with `id_path`. This argument is computed if it's not given. +* `serial` - (Optional) Serial number of the block device. Used in conjunction with `model` argument. Conflicts with `id_path`. This argument is computed if it's not given. +* `id_path` - (Optional) Only used if `model` and `serial` cannot be provided. This should be a path that is fixed and doesn't change depending on the boot order or kernel version. This argument is computed if it's not given. +* `tags` - (Optional) A set of tag names assigned to the new block device. This argument is computed if it's not given. + +### partitions + +* `size_gigabytes` - (Required) The partition size (given in GB). +* `bootable` - (Optional) Boolean value indicating if the partition is set as bootable. +* `tags` - (Optional) The tags assigned to the new block device partition. +* `fs_type` - (Optional) The file system type (e.g. `ext4`). If this is not set, the partition is unformatted. +* `label` - (Optional) The label assigned if the partition is formatted. +* `mount_point` - (Optional) The mount point used. If this is not set, the partition is not mounted. This is used only the partition is formatted. +* `mount_options` - (Optional) The options used for the partition mount. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - Block device ID. +* `uuid` - Block device UUID. +* `path` - Block device path. + +## Import + +Block devices can be imported with the machine identifier (system ID, hostname, or FQDN) and the block device identifier (ID or name). e.g. + +```shell +terraform import maas_block_device.vdb machine-06:vdb +``` diff --git a/docs/resources/maas_dns_domain.md b/docs/resources/maas_dns_domain.md new file mode 100644 index 00000000..de5ebd0d --- /dev/null +++ b/docs/resources/maas_dns_domain.md @@ -0,0 +1,37 @@ + +# Resource: maas_dns_domain + +Provides a resource to manage MAAS DNS domains. + +## Example Usage + +```terraform +resource "maas_dns_domain" "cloudbase" { + name = "cloudbase" + ttl = 3600 + authoritative = true +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the new DNS domain. +* `ttl` - (Optional) The default TTL for the new DNS domain. +* `authoritative` - (Optional) Boolean value indicating if the new DNS domain is authoritative. Defaults to `false`. +* `is_default` - (Optional) Boolean value indicating if the new DNS domain will be set as the default in the MAAS environment. Defaults to `false`. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The DNS domain ID. + +## Import + +DNS domains can be imported using their ID or name. e.g. + +```shell +terraform import maas_dns_domain.cloudbase cloudbase +``` diff --git a/docs/resources/maas_dns_record.md b/docs/resources/maas_dns_record.md new file mode 100644 index 00000000..566d4e99 --- /dev/null +++ b/docs/resources/maas_dns_record.md @@ -0,0 +1,39 @@ + +# Resource: maas_dns_record + +Provides a resource to manage MAAS DNS domain records. + +## Example Usage + +```terraform +resource "maas_dns_record" "test_a" { + type = "A/AAAA" + data = "10.99.11.33" + fqdn = "test-a.${maas_dns_domain.cloudbase.name}" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `type` - (Required) The DNS record type. Valid options are: `A/AAAA`, `CNAME`, `MX`, `NS`, `SRV`, `SSHFP`, `TXT`. +* `data` - (Required) The data set for the new DNS record. +* `name` - (Optional) The new DNS record resource name. Used in conjunction with `domain`. It conflicts with `fqdn` argument. +* `domain` - (Optional) The domain of the new DNS record. Used in conjunction with `name`. It conflicts with `fqdn` argument. +* `fqdn` - (Optional) The fully qualified domain name of the new DNS record. This contains the name and the domain of the new DNS record. It conflicts with `name` and `domain` arguments. +* `ttl` - (Optional) The TTL of the new DNS record. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The DNS record ID. + +## Import + +DNS records can be imported using the type and the identifier (ID or FQDN). e.g. + +```shell +terraform import maas_dns_record.test_a A/AAAA:test-a.cloudbase +``` diff --git a/docs/resources/maas_fabric.md b/docs/resources/maas_fabric.md new file mode 100644 index 00000000..ab7894c3 --- /dev/null +++ b/docs/resources/maas_fabric.md @@ -0,0 +1,32 @@ + +# Resource: maas_fabric + +Provides a resource to manage MAAS network fabrics. + +## Example Usage + +```terraform +resource "maas_fabric" "tf_fabric" { + name = "tf-fabric" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The fabric name. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The fabric ID. + +## Import + +An existing network fabric can be imported using its name or ID. e.g. + +```shell +terraform import maas_fabric.tf_fabric tf-fabric +``` diff --git a/docs/resources/maas_instance.md b/docs/resources/maas_instance.md index da76bb0c..111dfad5 100644 --- a/docs/resources/maas_instance.md +++ b/docs/resources/maas_instance.md @@ -1,28 +1,74 @@ -# `maas_instance` -It is used to deploy and release machines already registered and configured in MAAS based on the specified parameters. If no parameters are given, a random machine will be allocated and deployed using the defaults. +# Resource: maas_instance -Example: +Provides a resource to deploy and release machines already configured in MAAS, based on the specified parameters. If no parameters are given, a random machine will be allocated and deployed using the defaults. -```hcl +**NOTE:** The MAAS provider currently provides both standalone resources and in-line resources for network interfaces. You cannot use in-line network interfaces in conjunction with any standalone network interfaces resources. Doing so will cause conflicts and will overwrite network configs. + +## Example Usage + +```terraform resource "maas_instance" "two_random_nodes_2G" { count = 2 - allocate_min_cpu_count = 1 - allocate_min_memory = 2048 - deploy_distro_series = "focal" + allocate_params { + min_cpu_count = 1 + min_memory = 2048 + } + deploy_params { + distro_series = "focal" + } } ``` -Parameters: - -| Name | Type | Required | Description -| ---- | ---- | -------- | ----------- -| `allocate_min_cpu_count` | `string` | `false` | The minimum CPU cores count used for MAAS machine allocation. -| `allocate_min_memory` | `int` | `false` | The minimum RAM memory (in MB) used for MAAS machine allocation. -| `allocate_hostname` | `string` | `false` | Hostname used for MAAS machine allocation. -| `allocate_zone` | `string` | `false` | Zone name used for MAAS machine allocation. -| `allocate_pool` | `string` | `false` | Pool name used for MAAS machine allocation. -| `allocate_tags` | `[]string` | `false` | List of tag names used for MAAS machine allocation. -| `deploy_distro_series` | `string` | `false` | Distro series used to deploy the MAAS machine. It defaults to `focal`. -| `deploy_hwe_kernel` | `string` | `false` | Hardware enablement kernel to use with the image. Only used when deploying Ubuntu. -| `deploy_user_data` | `string` | `false` | Cloud-init user data script that gets run on the machine once it has deployed. +## Argument Reference + +The following arguments are supported: + +* `allocate_params` - (Optional) Nested argument with the constraints used to machine allocation. Defined below. +* `deploy_params` - (Optional) Nested argument with the config used to deploy the allocated machine. Defined below. +* `network_interfaces` - (Optional) Specifies a network interface configuration done before the machine is deployed. Parameters defined below. This argument is processed in [attribute-as-blocks mode](https://www.terraform.io/docs/configuration/attr-as-blocks.html). + +### allocate_params + +* `min_cpu_count` - (Optional) The minimum number of cores used to allocate the MAAS machine. +* `min_memory` - (Optional) The minimum RAM memory size (in MB) used to allocate the MAAS machine. +* `hostname` - (Optional) The hostname of the MAAS machine to be allocated. +* `zone` - (Optional) The zone name of the MAAS machine to be allocated. +* `pool` - (Optional) The pool name of the MAAS machine to be allocated. +* `tags` - (Optional) A set of tag names that must be assigned on the MAAS machine to be allocated. + +### deploy_params + +* `distro_series` - (Optional) The distro series used to deploy the allocated MAAS machine. If it's not given, the MAAS server default value is used. +* `hwe_kernel` - (Optional) Hardware enablement kernel to use with the image. Only used when deploying Ubuntu. +* `user_data` - (Optional) Cloud-init user data script that gets run on the machine once it has deployed. A good practice is to set this with `file("/tmp/user-data.txt")`, where `/tmp/user-data.txt` is a cloud-init script. + +### network_interfaces + +* `name` - (Required) The name of the network interface to be configured on the allocated machine. +* `subnet_cidr` - (Optional) An existing subnet CIDR used to configure the network interface. Unless `ip_address` is defined, a free IP address is allocated from the subnet. +* `ip_address` - (Optional) Static IP address to be configured on the network interface. If this is set, the `subnet_cidr` is required. + +**NOTE:** If both `subnet_cidr` and `ip_address` are not defined, the interface will not be configured on the allocated machine. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The deployed MAAS machine system ID. +* `fqdn` - The deployed MAAS machine FQDN. +* `hostname` - The deployed MAAS machine hostname. +* `zone` - The deployed MAAS machine zone name. +* `pool` - The deployed MAAS machine pool name. +* `tags` - A set of tag names associated to the deployed MAAS machine. +* `cpu_count` - The number of CPU cores of the deployed MAAS machine. +* `memory` - The RAM memory size (in GiB) of the deployed MAAS machine. +* `ip_addresses` - A set of IP addressed assigned to the deployed MAAS machine. + +## Import + +The machines imported as `maas_instance` resources must be already deployed. They can be imported using one of the deployed machine attributes: system ID, hostname, or FQDN. e.g. + +```shell +terraform import maas_instance.virsh_vm machine-01 +``` diff --git a/docs/resources/maas_machine.md b/docs/resources/maas_machine.md index ea1190bd..50969f12 100644 --- a/docs/resources/maas_machine.md +++ b/docs/resources/maas_machine.md @@ -1,11 +1,11 @@ -# `maas_machine` +# Resource: maas_machine -Creates a new MAAS machine. +Provides a resource to manage MAAS machines. -Example: +## Example Usage -```hcl +```terraform resource "maas_machine" "virsh" { power_type = "virsh" power_parameters = { @@ -16,16 +16,30 @@ resource "maas_machine" "virsh" { } ``` -Parameters: - -| Name | Type | Required | Description -| ---- | ---- | -------- | ----------- -| `power_type` | `string` | `true` | A power management type (e.g. `virsh`, `ipmi`). -| `power_parameters` | `map[string]string` | `true` | The parameter(s) for the `power_type`. Note that this is dynamic as the available parameters depend on the selected value of the Machine's `power_type`. See [Power types](https://maas.io/docs/api#power-types) section for a list of the available power parameters for each power type. -| `pxe_mac_address` | `string` | `true` | The MAC address of the machine's PXE boot NIC. -| `architecture` | `string` | `false` | A string containing the architecture type of the machine. -| `min_hwe_kernel` | `string` | `false` | A string containing the minimum kernel version allowed to be ran on this machine. -| `hostname` | `string` | `false` | A hostname. If not given, one will be generated. -| `domain` | `string` | `false` | The domain of the machine. If not given, the default domain is used. -| `zone` | `string` | `false` | Name of a valid physical zone in which to place this machine. -| `pool` | `string` | `false` | The resource pool to which the machine should belong. +## Argument Reference + +The following arguments are supported: + +* `power_type` - (Required) A power management type (e.g. `ipmi`). +* `power_parameters` - (Required) A map with the parameters specific to the `power_type`. See [Power types](https://maas.io/docs/api#power-types) section for a list of the available power parameters for each power type. +* `pxe_mac_address` - (Required) The MAC address of the machine's PXE boot NIC. +* `architecture` - (Optional) The architecture type of the machine. Defaults to `amd64/generic`. +* `min_hwe_kernel` - (Optional) The minimum kernel version allowed to run on this machine. Only used when deploying Ubuntu. This is computed if it's not set. +* `hostname` - (Optional) The machine hostname. This is computed if it's not set. +* `domain` - (Optional) The domain of the machine. This is computed if it's not set. +* `zone` - (Optional) The zone of the machine. This is computed if it's not set. +* `pool` - (Optional) The resource pool of the machine. This is computed if it's not set. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The MAAS machine system ID. + +## Import + +MAAS machines can be imported using one of the attributes: system ID, hostname, or FQDN. e.g. + +```shell +terraform import maas_machine.virsh_vm1 vm1.maas +``` diff --git a/docs/resources/maas_network_interface_link.md b/docs/resources/maas_network_interface_link.md index b2a3d046..14a6bf27 100644 --- a/docs/resources/maas_network_interface_link.md +++ b/docs/resources/maas_network_interface_link.md @@ -1,27 +1,42 @@ -# `maas_network_interface_link` -Configures a machine network interface with an IP address from a given subnet. +# Resource: maas_network_interface_link -Example: +Provides a resource to manage network configuration on a network interface. -```hcl +## Example Usage + +```terraform resource "maas_network_interface_link" "virsh_vm1_nic1" { - machine_id = maas_machine.virsh_vm1.id - network_interface_id = maas_network_interface_physical.virsh_vm1_nic1.id - subnet_id = data.maas_subnet.pxe.id + machine = maas_machine.virsh_vm1.id + network_interface = maas_network_interface_physical.virsh_vm1_nic1.id + subnet = data.maas_subnet.pxe.id mode = "STATIC" ip_address = "10.121.10.29" default_gateway = true } ``` -Parameters: +## Argument Reference + +The following arguments are supported: + +* `machine` - (Required) The identifier (system ID, hostname, or FQDN) of the machine with the network interface. +* `network_interface` - (Required) The identifier (MAC address, name, or ID) of the network interface. +* `subnet` - (Required) The identifier (CIDR or ID) of the subnet to be connected. +* `mode` - (Optional) Connection mode to subnet. It defaults to `AUTO`. Valid options are: + * `AUTO` - Random static IP address from the subnet. + * `DHCP` - IP address from the DHCP on the given subnet. + * `STATIC` - Use `ip_address` as static IP address. + * `LINK_UP` - Bring the interface up only on the given subnet. No IP address will be assigned. +* `default_gateway` - (Optional) Boolean value. When enabled, it sets the subnet gateway IP address as the default gateway for the machine the interface belongs to. This option can only be used with the `AUTO` and `STATIC` modes. Defaults to `false`. +* `ip_address` - (Optional) Valid IP address (from the given subnet) to be configured on the network interface. Only used when `mode` is set to `STATIC`. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The network interface link ID. + +## Import -| Name | Type | Required | Description -| ---- | ---- | -------- | ----------- -| `machine_id` | `string` | `true` | Machine system id. -| `network_interface_id` | `int` | `true` | Network interface id. -| `subnet_id` | `int` | `true` | Subnet id. -| `mode` | `string` | `false` | Connection mode to subnet. It defaults to `AUTO`. Valid options are: `AUTO` (random static IP address from the subnet), `DHCP` (DHCP on the given subnet), `STATIC` (use `ip_address` as static IP address). -| `ip_address` | `string` | `false` | IP address for the interface in the given subnet. Only used when `mode` is `STATIC`. -| `default_gateway` | `bool` | `false` | When enabled, it sets the subnet gateway IP address as the default gateway for the machine this interface belongs to. This option can only be used with the `AUTO` and `STATIC` modes. +This resource doesn't support the import operation. diff --git a/docs/resources/maas_network_interface_physical.md b/docs/resources/maas_network_interface_physical.md index 451a1d19..53e14a3f 100644 --- a/docs/resources/maas_network_interface_physical.md +++ b/docs/resources/maas_network_interface_physical.md @@ -1,15 +1,17 @@ -# `maas_network_interface_physical` -Configures a physical network interface from an existing MAAS machine. +# Resource: maas_network_interface_physical -Example: +Provides a resource to manage a physical network interface from an existing MAAS machine. -```hcl +## Example Usage + +```terraform resource "maas_network_interface_physical" "virsh_vm1_nic1" { - machine_id = maas_machine.virsh_vm1.id + machine = maas_machine.virsh_vm1.id mac_address = "52:54:00:89:f5:3e" - name = "eth0" vlan = data.maas_vlan.default.id + name = "eth0" + mtu = 1450 tags = [ "nic1-tag1", "nic1-tag2", @@ -18,15 +20,27 @@ resource "maas_network_interface_physical" "virsh_vm1_nic1" { } ``` -Parameters: - -| Name | Type | Required | Description -| ---- | ---- | -------- | ----------- -| `machine_id` | `string` | `true` | Machine system id. -| `mac_address` | `string` | `true` | The physical networking interface MAC address. -| `name` | `string` | `false` | The physical networking interface name. -| `tags` | `[]string` | `false` | Tags for the interface. -| `vlan` | `string` | `false` | VLAN the interface is connected to. Defaults to `untagged`. -| `mtu` | `int` | `false` | Maximum transmission unit. Defaults to `1500`. -| `accept_ra` | `bool` | `false` | Accept router advertisements (IPv6 only). -| `autoconf` | `bool` | `false` | Perform stateless autoconfiguration (IPv6 only). +## Argument Reference + +The following arguments are supported: + +* `machine` - (Required) The identifier (system ID, hostname, or FQDN) of the machine with the physical network interface. +* `mac_address` - (Required) The physical network interface MAC address. +* `vlan` - (Optional) VLAN the physical network interface is connected to. Defaults to `untagged`. +* `name` - (Optional) The physical network interface name. This argument is computed if it's not set. +* `mtu` - (Optional) The MTU of the physical network interface. This argument is computed if it's not set. +* `tags` - (Optional) A set of tag names to be assigned to the physical network interface. This argument is computed if it's not set. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The physical network interface ID. + +## Import + +A physical network interface can be imported using the machine identifier (system ID, hostname, or FQDN) and its own identifier (MAC address, name, or ID). e.g. + +```shell +terraform import maas_network_interface_physical.virsh_vm1 vm1:eth0 +``` diff --git a/docs/resources/maas_space.md b/docs/resources/maas_space.md new file mode 100644 index 00000000..bd3d26b4 --- /dev/null +++ b/docs/resources/maas_space.md @@ -0,0 +1,32 @@ + +# Resource: maas_space + +Provides a resource to manage MAAS network spaces. + +## Example Usage + +```terraform +resource "maas_space" "tf_space" { + name = "tf-space" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the new space. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The space ID. + +## Import + +Spaces can be imported using the name or ID. e.g. + +```shell +terraform import maas_space.tf_space tf-space +``` diff --git a/docs/resources/maas_subnet.md b/docs/resources/maas_subnet.md new file mode 100644 index 00000000..5c22e760 --- /dev/null +++ b/docs/resources/maas_subnet.md @@ -0,0 +1,71 @@ + +# Resource: maas_subnet + +Provides a resource to manage MAAS network subnets. + +**NOTE:** The MAAS provider currently supports both standalone resources and in-line resources for subnet IP ranges. You cannot use in-line `ip_ranges` in conjunction with standalone `maas_subnet_ip_range` resources. Doing so will cause conflicts and will overwrite subnet IP ranges. + +## Example Usage + +```terraform +resource "maas_subnet" "tf_subnet" { + cidr = "10.88.88.0/24" + fabric = maas_fabric.tf_fabric.id + vlan = maas_vlan.tf_vlan.vid + name = "tf_subnet" + gateway_ip = "10.88.88.1" + dns_servers = [ + "1.1.1.1", + ] + + ip_ranges { + type = "reserved" + start_ip = "10.88.88.1" + end_ip = "10.88.88.50" + } + ip_ranges { + type = "dynamic" + start_ip = "10.88.88.200" + end_ip = "10.88.88.254" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `cidr` - (Required) The subnet CIDR. +* `name` - (Optional) The subnet name. +* `fabric` - (Optional) The fabric identifier (ID or name) for the new subnet. +* `vlan` - (Optional) The VLAN identifier (ID or traffic segregation ID) for the new subnet. If this is set, the `fabric` argument is required. +* `ip_ranges` - (Optional) A set of IP ranges configured on the new subnet. Parameters defined below. This argument is processed in [attribute-as-blocks mode](https://www.terraform.io/docs/configuration/attr-as-blocks.html). +* `rdns_mode` - (Optional) How reverse DNS is handled for this subnet. Defaults to `2`. Valid options are: + * `0` - Disabled, no reverse zone is created. + * `1` - Enabled, generate reverse zone. + * `2` - RFC2317, extends `1` to create the necessary parent zone with the appropriate CNAME resource records for the network, if the network is small enough to require the support described in RFC2317. +* `allow_dns` - (Optional) Boolean value that indicates if the MAAS DNS resolution is enabled for this subnet. Defaults to `true`. +* `allow_proxy` - (Optional) Boolean value that indicates if `maas-proxy` allows requests from this subnet. Defaults to `true`. +* `gateway_ip` - (Optional) Gateway IP address for the new subnet. This argument is computed if it's not set. +* `dns_servers` - (Optional) List of IP addresses set as DNS servers for the new subnet. This argument is computed if it's not set. + +### ip_ranges + +* `type` - (Required) The IP range type. Valid options are: `dynamic`, `reserved`. +* `start_ip` - (Required) The start IP for the new IP range (inclusive). +* `end_ip` - (Required) The end IP for the new IP range (inclusive). +* `comment` - (Optional) A description of this range. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The subnet ID. + +## Import + +MAAS network subnets can be imported using the ID or CIDR. e.g. + +```shell +terraform import maas_subnet.tf_subnet 10.77.77.0/24 +``` diff --git a/docs/resources/maas_subnet_ip_range.md b/docs/resources/maas_subnet_ip_range.md new file mode 100644 index 00000000..68af4d15 --- /dev/null +++ b/docs/resources/maas_subnet_ip_range.md @@ -0,0 +1,39 @@ + +# Resource: maas_subnet_ip_range + +Provides a resource to manage MAAS network subnets IP ranges. + +## Example Usage + +```terraform +resource "maas_subnet_ip_range" "dynamic_ip_range" { + subnet = maas_subnet.tf_subnet_2.id + type = "dynamic" + start_ip = "10.77.77.2" + end_ip = "10.77.77.60" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `subnet` - (Required) The subnet identifier (ID or CIDR) for the new IP range. +* `type` - (Required) The IP range type. Valid options are: `dynamic`, `reserved`. +* `start_ip` - (Required) The start IP for the new IP range (inclusive). +* `end_ip` - (Required) The end IP for the new IP range (inclusive). +* `comment` - (Optional) A description of this range. This argument is computed if it's not set. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The subnet IP range ID. + +## Import + +IP ranges can be imported with the start IP and the end IP. e.g. + +```shell +terraform import maas_subnet_ip_range.dynamic_ip_range 10.77.77.2:10.77.77.60 +``` diff --git a/docs/resources/maas_tag.md b/docs/resources/maas_tag.md index fb02d0e5..4cfa5131 100644 --- a/docs/resources/maas_tag.md +++ b/docs/resources/maas_tag.md @@ -1,14 +1,14 @@ -# `maas_tag` +# Resource: maas_tag -Create a new MAAS tag, and use it to tag MAAS machines. +Provides a resource to manage a MAAS tag. -Example: +## Example Usage -```hcl +```terraform resource "maas_tag" "kvm" { name = "kvm" - machine_ids = [ + machines = [ maas_vm_host_machine.kvm[0].id, maas_vm_host_machine.kvm[1].id, maas_machine.virsh_vm1.id, @@ -17,9 +17,23 @@ resource "maas_tag" "kvm" { } ``` -Parameters: +## Argument Reference -| Name | Type | Required | Description -| ---- | ---- | -------- | ----------- -| `name` | `string` | `true` | The new tag name. Because the name will be used in urls, it should be short. -| `machine_ids` | `[]string` | `false` | List of MAAS machines' ids that will be tagged. +The following arguments are supported: + +* `name` - (Required) The new tag name. Because the name will be used in urls, it should be short. +* `machines` - (Optional) List of MAAS machines' identifiers (system ID, hostname, or FQDN) that will be tagged with the new tag. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The tag name. + +## Import + +An existing tag can be imported using its name. e.g. + +```shell +terraform import maas_tag.kvm kvm +``` diff --git a/docs/resources/maas_vlan.md b/docs/resources/maas_vlan.md new file mode 100644 index 00000000..894c7aa5 --- /dev/null +++ b/docs/resources/maas_vlan.md @@ -0,0 +1,40 @@ + +# Resource: maas_vlan + +Provides a resource to manage MAAS network VLANs. + +## Example Usage + +```terraform +resource "maas_vlan" "tf_vlan" { + fabric = maas_fabric.tf_fabric.id + vid = 14 + name = "tf-vlan14" + space = maas_space.tf_space.name +} +``` + +## Argument Reference + +The following arguments are supported: + +* `fabric` - (Required) The identifier (name or ID) of the fabric for the new VLAN. +* `vid` - (Required) The traffic segregation ID for the new VLAN. +* `mtu` - (Optional) The MTU to use on the new VLAN. This argument is computed if it's not set. +* `dhcp_on` - (Optional) Boolean value. Whether or not DHCP should be managed on the new VLAN. This argument is computed if it's not set. +* `name` - (Optional) The name of the new VLAN. This argument is computed if it's not set. +* `space` - (Optional) The space of the new VLAN. Passing in an empty string (or the string `undefined`) will cause the VLAN to be placed in the `undefined` space. This argument is computed if it's not set. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The VLAN MAAS resource ID. + +## Import + +Existing MAAS VLANs can be imported using the fabric identifier (ID or name) and the VLAN identifier (ID or traffic segregation ID). e.g. + +```shell +terraform import maas_vlan.tf_vlan tf-fabric:14 +``` diff --git a/docs/resources/maas_vm_host.md b/docs/resources/maas_vm_host.md index e90e3f5b..49786bd6 100644 --- a/docs/resources/maas_vm_host.md +++ b/docs/resources/maas_vm_host.md @@ -1,31 +1,58 @@ -# `maas_vm_host` +# Resource: maas_vm_host -Creates a new MAAS VM host. +Provides a resource to manage MAAS VM hosts. -Example: +## Example Usage -```hcl +### Using pre-deployed VM host + +```terraform resource "maas_vm_host" "kvm" { type = "virsh" power_address = "qemu+ssh://ubuntu@10.113.1.10/system" - name = "kvm-host-01" } ``` -Parameters: - -| Name | Type | Required | Description -| ---- | ---- | -------- | ----------- -| `type` | `string` | `true` | The type of VM host to create: `lxd` or `virsh`. -| `machine` | `string` | `false` | The identifier (`hostname`, `fqdn` or `system_id`) of a registered `Ready` MAAS machine. This is going to be deployed and registered as a new VM host. -| `power_address` | `string` | `false` | Address that gives MAAS access to the VM host power control. For example: `qemu+ssh://172.16.99.2/system`. Cannot be set if `machine` parameter is used. -| `power_user` | `string` | `false` | Username to use for power control of the VM host. Cannot be set if `machine` parameter is used. -| `power_pass` | `string` | `false` | Password to use for power control of the VM host. Cannot be set if `machine` parameter is used. -| `name` | `string` | `false` | The new VM host name. -| `zone` | `string` | `false` | The new VM host zone. -| `pool` | `string` | `false` | The name of the resource pool the new VM host will belong to. Machines composed from this VM host will be assigned to this resource pool by default. -| `tags` | `[]string` | `false` | A list of tags to assign to the new VM host. -| `cpu_over_commit_ratio` | `float` | `false` | CPU overcommit ratio. -| `memory_over_commit_ratio` | `float` | `false` | RAM memory overcommit ratio. -| `default_macvlan_mode` | `string` | `false` | Default macvlan mode for VM hosts that use it: `bridge`, `passthru`, `private`, `vepa`. +### Deploy a new VM host from a ready MAAS machine + +```terraform +resource "maas_vm_host" "maas_machine" { + type = "virsh" + machine = "machine-05" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `type` - (Required) The VM host type. Supported values are: `lxd`, `virsh`. +* `machine` - (Optional) The identifier (hostname, FQDN or system ID) of a registered ready MAAS machine. This is going to be deployed and registered as a new VM host. This argument conflicts with: `power_address`, `power_user`, `power_pass`. +* `power_address` - (Optional) Address that gives MAAS access to the VM host power control. For example: `qemu+ssh://172.16.99.2/system`. The address given here must reachable by the MAAS server. It can't be set if `machine` argument is used. +* `power_user` - (Optional) User name to use for power control of the VM host. Cannot be set if `machine` parameter is used. +* `power_pass` - (Optional) User password to use for power control of the VM host. Cannot be set if `machine` parameter is used. +* `name` - (Optional) The new VM host name. This is computed if it's not set. +* `zone` - (Optional) The new VM host zone name. This is computed if it's not set. +* `pool` - (Optional) The new VM host pool name. This is computed if it's not set. +* `tags` - (Optional) A set of tag names to assign to the new VM host. This is computed if it's not set. +* `cpu_over_commit_ratio` - (Optional) The new VM host CPU overcommit ratio. This is computed if it's not set. +* `memory_over_commit_ratio` - (Optional) The new VM host RAM memory overcommit ratio. This is computed if it's not set. +* `default_macvlan_mode` - (Optional) The new VM host default macvlan mode. Supported values are: `bridge`, `passthru`, `private`, `vepa`. This is computed if it's not set. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The VM host ID. +* `resources_cores_total` - The VM host total number of CPU cores. +* `resources_memory_total` - The VM host total RAM memory (in MB). +* `resources_local_storage_total` - The VM host total local storage (in bytes). + +## Import + +VM hosts can be imported using the ID or the name. e.g. + +```shell +terraform import maas_vm_host.kvm vm-host-01 +``` diff --git a/docs/resources/maas_vm_host_machine.md b/docs/resources/maas_vm_host_machine.md index fba1dd45..fe4fea0f 100644 --- a/docs/resources/maas_vm_host_machine.md +++ b/docs/resources/maas_vm_host_machine.md @@ -1,29 +1,68 @@ -# `maas_vm_host_machine` -Composes a new MAAS machine from an existing MAAS VM host. +# Resource: maas_vm_host_machine -Example: +Provides a resource to manage MAAS VM host machines. -```hcl +## Example Usage + +```terraform resource "maas_vm_host_machine" "kvm" { vm_host = maas_vm_host.kvm.id cores = 1 memory = 2048 - storage = "disk1:32,disk2:20" + + network_interfaces { + name = "eth0" + subnet_cidr = data.maas_subnet.pxe.cidr + } + + storage_disks { + size_gigabytes = 10 + } + storage_disks { + size_gigabytes = 15 + } } ``` -Parameters: - -| Name | Type | Required | Description -| ---- | ---- | -------- | ----------- -| `vm_host` | `string` | `true` | The `id` or `name` of an existing MAAS VM host. -| `cores` | `int` | `false` | The number of CPU cores (defaults to `1`). -| `pinned_cores` | `int` | `false` | List of host CPU cores to pin the VM to. If this is passed, the `cores` parameter is ignored. -| `memory` | `int` | `false` | The amount of memory, specified in MiB. -| `storage` | `string` | `false` | A list of storage constraint identifiers in the form `label:size(tag,tag,...),label:size(tag,tag,...)`. For more information, see [this](https://maas.io/docs/composable-hardware#heading--storage). -| `interfaces` | `string` | `false` | A labeled constraint map associating constraint labels with desired interface properties. MAAS will assign interfaces that match the given interface properties. For more information, see [this](https://maas.io/docs/composable-hardware#heading--interfaces). -| `hostname` | `string` | `false` | The hostname of the newly composed machine. -| `domain` | `string` | `false` | The name of the domain in which to put the newly composed machine. -| `zone` | `string` | `false` | The name of the zone in which to put the newly composed machine. -| `pool` | `string` | `false` | The name of the pool in which to put the newly composed machine. +## Argument Reference + +The following arguments are supported: + +* `vm_host` - (Required) ID or name of the VM host used to compose the new machine. +* `cores` - (Optional) The number of CPU cores (defaults to 1). +* `pinned_cores` - (Optional) List of host CPU cores to pin the VM host machine to. If this is passed, the `cores` parameter is ignored. +* `memory` - (Optional) The VM host machine RAM memory, specified in MB (defaults to 2048). +* `network_interfaces` - (Optional) A list of network interfaces for new the VM host. This argument only works when the VM host is deployed from a registered MAAS machine. Parameters defined below. This argument is processed in [attribute-as-blocks mode](https://www.terraform.io/docs/configuration/attr-as-blocks.html). +* `storage_disks` - (Optional) A list of storage disks for the new VM host. Parameters defined below. This argument is processed in [attribute-as-blocks mode](https://www.terraform.io/docs/configuration/attr-as-blocks.html). +* `hostname` - (Optional) The VM host machine hostname. This is computed if it's not set. +* `domain` - (Optional) The VM host machine domain. This is computed if it's not set. +* `zone` - (Optional) The VM host machine zone. This is computed if it's not set. +* `pool` - (Optional) The VM host machine pool. This is computed if it's not set. + +### network_interfaces + +* `name` - (Required) The network interface name. +* `fabric` - (Optional) The fabric for the network interface. +* `vlan` - (Optional) The VLAN for the network interface. +* `subnet_cidr` - (Optional) The subnet CIDR for the network interface. +* `ip_address` - (Optional) Static IP configured on the new network interface. + +### storage_disks + +* `size_gigabytes` - (Required) The storage disk size, specified in GB. +* `pool` - (Optional) The VM host storage pool name. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The VM host machine system ID. + +## Import + +VM host machines can be imported using the identifier of the MAAS machine (system ID, hostname, or FQDN). e.g. + +```shell +terraform import maas_vm_host_machine.test machine-02 +``` From a49560477051db2182a9a312dcc979382c9ce5f8 Mon Sep 17 00:00:00 2001 From: Ionut Balutoiu Date: Wed, 7 Jul 2021 11:22:56 +0300 Subject: [PATCH 14/14] Add maas_user managed resource --- docs/README.md | 1 + docs/resources/maas_user.md | 32 ++++++++++ go.mod | 2 +- go.sum | 4 +- maas/provider.go | 1 + maas/resource_maas_user.go | 118 ++++++++++++++++++++++++++++++++++++ maas/utils.go | 25 ++++++++ 7 files changed, 180 insertions(+), 3 deletions(-) create mode 100644 docs/resources/maas_user.md create mode 100644 maas/resource_maas_user.go diff --git a/docs/README.md b/docs/README.md index 61026355..a5d275f3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -23,3 +23,4 @@ 1. [maas_space](/docs/resources/maas_space.md) 1. [maas_block_device](/docs/resources/maas_block_device.md) 1. [maas_tag](/docs/resources/maas_tag.md) +1. [maas_user](/docs/resources/maas_user.md) diff --git a/docs/resources/maas_user.md b/docs/resources/maas_user.md new file mode 100644 index 00000000..6bc4c739 --- /dev/null +++ b/docs/resources/maas_user.md @@ -0,0 +1,32 @@ + +# Resource: maas_user + +Provides a resource to manage MAAS users. + +## Example Usage + +```terraform +resource "maas_user" "cloudbase" { + name = "cloudbase" + password = "Passw0rd123" + email = "admin@cloudbase.local" + is_admin = true +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The user name. +* `password` - (Required) The user password. +* `email` - (Required) The user e-mail address. +* `is_admin` - (Optional) Boolean value indicating if the user is a MAAS administrator. Defaults to `false`. + +## Import + +Users can be imported using their name. e.g. + +```shell +terraform import maas_user.cloudbase cloudbase +``` diff --git a/go.mod b/go.mod index 7045bd51..46652639 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,6 @@ go 1.16 require ( github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/terraform-plugin-sdk/v2 v2.6.1 - github.com/ionutbalutoiu/gomaasclient v0.0.0-20210706123835-65b3275559c2 + github.com/ionutbalutoiu/gomaasclient v0.0.0-20210707081625-0c69fffd5a8c github.com/stretchr/testify v1.7.0 ) diff --git a/go.sum b/go.sum index ee3e08a4..c3a688f7 100644 --- a/go.sum +++ b/go.sum @@ -204,8 +204,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/ionutbalutoiu/gomaasclient v0.0.0-20210706123835-65b3275559c2 h1:Ed/X3MVwFMOh/cgbuGz/fO6mwYkAaj7sy95MSAKOtCU= -github.com/ionutbalutoiu/gomaasclient v0.0.0-20210706123835-65b3275559c2/go.mod h1:bj39184ChgZMBH01zYHoUV2h4mSmAYisQSHs/sRzYQE= +github.com/ionutbalutoiu/gomaasclient v0.0.0-20210707081625-0c69fffd5a8c h1:BUlfd505LBka9F3Z9QvV6LNtNtTJ0S7YnwLBJiExPfM= +github.com/ionutbalutoiu/gomaasclient v0.0.0-20210707081625-0c69fffd5a8c/go.mod h1:bj39184ChgZMBH01zYHoUV2h4mSmAYisQSHs/sRzYQE= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= diff --git a/maas/provider.go b/maas/provider.go index d1a9ad05..4f20c1ad 100644 --- a/maas/provider.go +++ b/maas/provider.go @@ -47,6 +47,7 @@ func Provider() *schema.Provider { "maas_space": resourceMaasSpace(), "maas_block_device": resourceMaasBlockDevice(), "maas_tag": resourceMaasTag(), + "maas_user": resourceMaasUser(), }, DataSourcesMap: map[string]*schema.Resource{ "maas_fabric": dataSourceMaasFabric(), diff --git a/maas/resource_maas_user.go b/maas/resource_maas_user.go new file mode 100644 index 00000000..eb10ceff --- /dev/null +++ b/maas/resource_maas_user.go @@ -0,0 +1,118 @@ +package maas + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/ionutbalutoiu/gomaasclient/client" + "github.com/ionutbalutoiu/gomaasclient/entity" +) + +func resourceMaasUser() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceUserCreate, + ReadContext: resourceUserRead, + DeleteContext: resourceUserDelete, + Importer: &schema.ResourceImporter{ + StateContext: func(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + client := m.(*client.Client) + user, err := getUser(client, d.Id()) + if err != nil { + return nil, err + } + tfState := map[string]interface{}{ + "id": user.UserName, + "name": user.UserName, + "email": user.Email, + "is_admin": user.IsSuperUser, + } + if err := setTerraformState(d, tfState); err != nil { + return nil, err + } + return []*schema.ResourceData{d}, nil + }, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "password": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + ForceNew: true, + }, + "email": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateDiagFunc: isEmailAddress, + }, + "is_admin": { + Type: schema.TypeBool, + Optional: true, + Default: false, + ForceNew: true, + }, + }, + } +} + +func resourceUserCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + + user, err := client.Users.Create(getUserParams(d)) + if err != nil { + return diag.FromErr(err) + } + d.SetId(user.UserName) + + return nil +} + +func resourceUserRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + + if _, err := client.User.Get(d.Id()); err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceUserDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + + if err := client.User.Delete(d.Id()); err != nil { + return diag.FromErr(err) + } + + return nil +} + +func getUserParams(d *schema.ResourceData) *entity.UserParams { + return &entity.UserParams{ + UserName: d.Get("name").(string), + Password: d.Get("password").(string), + Email: d.Get("email").(string), + IsSuperUser: d.Get("is_admin").(bool), + } +} + +func getUser(client *client.Client, userName string) (*entity.User, error) { + users, err := client.Users.Get() + if err != nil { + return nil, err + } + for _, u := range users { + if u.UserName == userName { + return &u, nil + } + } + return nil, fmt.Errorf("user (%s) was not found", userName) +} diff --git a/maas/utils.go b/maas/utils.go index e001907c..893c8904 100644 --- a/maas/utils.go +++ b/maas/utils.go @@ -3,6 +3,7 @@ package maas import ( "encoding/base64" "fmt" + "net/mail" "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/go-cty/cty/gocty" @@ -65,6 +66,30 @@ func isElementIPAddress(i interface{}, p cty.Path) diag.Diagnostics { return diags } +func isEmailAddress(i interface{}, p cty.Path) diag.Diagnostics { + var diags diag.Diagnostics + attr := p[len(p)-1].(cty.GetAttrStep) + + v, ok := i.(string) + if !ok { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: fmt.Sprintf("expected type of %q to be string", attr.Name), + AttributePath: p, + }) + } + + if _, err := mail.ParseAddress(i.(string)); err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: fmt.Sprintf("expected %s to be a valid e-mail address, got: %s", attr.Name, v), + AttributePath: p, + }) + } + + return diags +} + func getNetworkInterface(client *client.Client, machineSystemID string, identifier string) (*entity.NetworkInterface, error) { networkInterfaces, err := client.NetworkInterfaces.Get(machineSystemID) if err != nil {