From 7229481d0ee7899a4580838f9bf49254f99a8dcb Mon Sep 17 00:00:00 2001 From: damans227 Date: Mon, 13 Jun 2022 19:13:54 -0400 Subject: [PATCH 01/39] create ssh keypair data-source --- .../data_source_cloudstack_ssh_keypair.go | 113 ++++++++++++++++++ cloudstack/provider.go | 3 +- 2 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 cloudstack/data_source_cloudstack_ssh_keypair.go diff --git a/cloudstack/data_source_cloudstack_ssh_keypair.go b/cloudstack/data_source_cloudstack_ssh_keypair.go new file mode 100644 index 00000000..c7696cbb --- /dev/null +++ b/cloudstack/data_source_cloudstack_ssh_keypair.go @@ -0,0 +1,113 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "encoding/json" + "fmt" + "log" + "regexp" + "strings" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceCloudstackSSHKeyPair() *schema.Resource { + return &schema.Resource{ + Read: dataSourceCloudstackSSHKeyPairRead, + + Schema: map[string]*schema.Schema{ + "filter": dataSourceFiltersSchema(), + + //Computed values + "fingerprint": { + Type: schema.TypeString, + Computed: true, + }, + + "name": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceCloudstackSSHKeyPairRead(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + p := cs.SSH.NewListSSHKeyPairsParams() + csSshKeyPairs, err := cs.SSH.ListSSHKeyPairs(p) + + if err != nil { + return fmt.Errorf("Failed to list ssh key pairs: %s", err) + } + filters := d.Get("filter") + var sshKeyPair *cloudstack.SSHKeyPair + + for _, k := range csSshKeyPairs.SSHKeyPairs { + match, err := applySshKeyPairsFilters(k, filters.(*schema.Set)) + if err != nil { + return err + } + if match { + sshKeyPair = k + } + } + + if sshKeyPair == nil { + return fmt.Errorf("No ssh key pair is matching with the specified regex") + } + log.Printf("[DEBUG] Selected ssh key pair: %s\n", sshKeyPair.Name) + + return sshKeyPairDescriptionAttributes(d, sshKeyPair) +} + +func sshKeyPairDescriptionAttributes(d *schema.ResourceData, sshKeyPair *cloudstack.SSHKeyPair) error { + d.SetId(sshKeyPair.Name) + d.Set("fingerprint", sshKeyPair.Fingerprint) + d.Set("name", sshKeyPair.Name) + + return nil +} + +func applySshKeyPairsFilters(sshKeyPair *cloudstack.SSHKeyPair, filters *schema.Set) (bool, error) { + var sshKeyPairJSON map[string]interface{} + k, _ := json.Marshal(sshKeyPair) + err := json.Unmarshal(k, &sshKeyPairJSON) + if err != nil { + return false, err + } + + for _, f := range filters.List() { + m := f.(map[string]interface{}) + r, err := regexp.Compile(m["value"].(string)) + if err != nil { + return false, fmt.Errorf("Invalid regex: %s", err) + } + updatedName := strings.ReplaceAll(m["name"].(string), "_", "") + sshKeyPairField := sshKeyPairJSON[updatedName].(string) + if !r.MatchString(sshKeyPairField) { + return false, nil + } + + } + return true, nil +} diff --git a/cloudstack/provider.go b/cloudstack/provider.go index 36d4c736..6ea0e7e4 100644 --- a/cloudstack/provider.go +++ b/cloudstack/provider.go @@ -78,7 +78,8 @@ func Provider() terraform.ResourceProvider { }, DataSourcesMap: map[string]*schema.Resource{ - "cloudstack_template": dataSourceCloudstackTemplate(), + "cloudstack_template": dataSourceCloudstackTemplate(), + "cloudstack_ssh_keypair": dataSourceCloudstackSSHKeyPair(), }, ResourcesMap: map[string]*schema.Resource{ From 3728391beb17b047194f72a30331b6bc4279f9fe Mon Sep 17 00:00:00 2001 From: damans227 Date: Tue, 31 May 2022 18:31:08 -0400 Subject: [PATCH 02/39] create a method stub for instance data-source --- cloudstack/data_source_cloudstack_instance.go | 35 +++++++++++++++++++ cloudstack/provider.go | 1 + 2 files changed, 36 insertions(+) create mode 100644 cloudstack/data_source_cloudstack_instance.go diff --git a/cloudstack/data_source_cloudstack_instance.go b/cloudstack/data_source_cloudstack_instance.go new file mode 100644 index 00000000..daffedf6 --- /dev/null +++ b/cloudstack/data_source_cloudstack_instance.go @@ -0,0 +1,35 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceCloudstackInstance() *schema.Resource { + return &schema.Resource{ + Read: dataSourceCloudstackInstanceRead, + Schema: map[string]*schema.Schema{}, + } +} + +func dataSourceCloudstackInstanceRead(d *schema.ResourceData, meta interface{}) error { + return nil +} diff --git a/cloudstack/provider.go b/cloudstack/provider.go index 6ea0e7e4..2df64a30 100644 --- a/cloudstack/provider.go +++ b/cloudstack/provider.go @@ -80,6 +80,7 @@ func Provider() terraform.ResourceProvider { DataSourcesMap: map[string]*schema.Resource{ "cloudstack_template": dataSourceCloudstackTemplate(), "cloudstack_ssh_keypair": dataSourceCloudstackSSHKeyPair(), + "cloudstack_instance": dataSourceCloudstackInstance(), }, ResourcesMap: map[string]*schema.Resource{ From eec69c8333ebc9ec7f1afee4f2e4fd731837941a Mon Sep 17 00:00:00 2001 From: damans227 Date: Wed, 1 Jun 2022 20:11:23 -0400 Subject: [PATCH 03/39] extending instance data-source to use its id as an input --- cloudstack/data_source_cloudstack_instance.go | 83 ++++++++++++++++++- 1 file changed, 81 insertions(+), 2 deletions(-) diff --git a/cloudstack/data_source_cloudstack_instance.go b/cloudstack/data_source_cloudstack_instance.go index daffedf6..b88f5725 100644 --- a/cloudstack/data_source_cloudstack_instance.go +++ b/cloudstack/data_source_cloudstack_instance.go @@ -20,16 +20,95 @@ package cloudstack import ( + "log" + + "github.com/apache/cloudstack-go/v2/cloudstack" "github.com/hashicorp/terraform/helper/schema" ) func dataSourceCloudstackInstance() *schema.Resource { return &schema.Resource{ - Read: dataSourceCloudstackInstanceRead, - Schema: map[string]*schema.Schema{}, + Read: dataSourceCloudstackInstanceRead, + Schema: map[string]*schema.Schema{ + + "instance_id": { + Type: schema.TypeString, + Required: true, + }, + + // Computed values + "account": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "display_name": { + Type: schema.TypeString, + Computed: true, + }, + "state": { + Type: schema.TypeString, + Computed: true, + }, + "host_id": { + Type: schema.TypeString, + Computed: true, + }, + "zone_id": { + Type: schema.TypeString, + Computed: true, + }, + "created": { + Type: schema.TypeString, + Computed: true, + }, + // "ip_address": { + // Type: schema.TypeString, + // Computed: true, + // }, + }, } } func dataSourceCloudstackInstanceRead(d *schema.ResourceData, meta interface{}) error { + log.Printf("Instance Data Source Read Started") + + cs := meta.(*cloudstack.CloudStackClient) + p := cs.VirtualMachine.NewListVirtualMachinesParams() + instance_id := d.Get("instance_id").(string) + + log.Printf("Instance ID =====================> %v", instance_id) + + csInstances, _ := cs.VirtualMachine.ListVirtualMachines(p) + + for _, instance := range csInstances.VirtualMachines { + log.Printf("Instance ===============> %v", instance) + if instance.Id == instance_id { + + d.Set("instance_id", instance.Id) + d.Set("account", instance.Account) + d.Set("created", instance.Created) + d.Set("display_name", instance.Displayname) + d.Set("state", instance.State) + d.Set("host_id", instance.Hostid) + d.Set("zone_id", instance.Zoneid) + + log.Printf("=========================================================================") + + log.Printf("Instance-ID being Set To: ===============> %v", d.Get("instance_id").(string)) + log.Printf("Instance-Created being Set To: ===============> %v", d.Get("created").(string)) + log.Printf("Instance-Display-Name being Set To: ===============> %v", d.Get("display_name").(string)) + log.Printf("Instance-State being Set To: ===============> %v", d.Get("state").(string)) + log.Printf("Instance-Host-ID being Set To: ===============> %v", d.Get("host_id").(string)) + log.Printf("Instance-Zone-ID being Set To: ===============> %v", d.Get("zone_id").(string)) + + log.Printf("=========================================================================") + } + } + log.Printf("Instance Data Source Read Ended") + return nil } From f6bf228273fced70335408e0accc9226f1d59cce Mon Sep 17 00:00:00 2001 From: damans227 Date: Thu, 2 Jun 2022 20:02:42 -0400 Subject: [PATCH 04/39] use SetId() to define the ID of the instance resource --- cloudstack/data_source_cloudstack_instance.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/cloudstack/data_source_cloudstack_instance.go b/cloudstack/data_source_cloudstack_instance.go index b88f5725..3ec5b654 100644 --- a/cloudstack/data_source_cloudstack_instance.go +++ b/cloudstack/data_source_cloudstack_instance.go @@ -41,10 +41,7 @@ func dataSourceCloudstackInstance() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "name": { - Type: schema.TypeString, - Computed: true, - }, + "display_name": { Type: schema.TypeString, Computed: true, @@ -87,7 +84,7 @@ func dataSourceCloudstackInstanceRead(d *schema.ResourceData, meta interface{}) for _, instance := range csInstances.VirtualMachines { log.Printf("Instance ===============> %v", instance) if instance.Id == instance_id { - + d.SetId(instance.Id) d.Set("instance_id", instance.Id) d.Set("account", instance.Account) d.Set("created", instance.Created) From 1c9dfbbf2625a99606093d26aa8ee73b701a76c1 Mon Sep 17 00:00:00 2001 From: damans227 Date: Fri, 3 Jun 2022 20:24:21 -0400 Subject: [PATCH 05/39] implement filters to allow filtering off of any exported attributes --- cloudstack/data_source_cloudstack_instance.go | 132 +++++++++++++----- 1 file changed, 97 insertions(+), 35 deletions(-) diff --git a/cloudstack/data_source_cloudstack_instance.go b/cloudstack/data_source_cloudstack_instance.go index 3ec5b654..f6699d94 100644 --- a/cloudstack/data_source_cloudstack_instance.go +++ b/cloudstack/data_source_cloudstack_instance.go @@ -20,7 +20,12 @@ package cloudstack import ( + "encoding/json" + "fmt" "log" + "regexp" + "strings" + "time" "github.com/apache/cloudstack-go/v2/cloudstack" "github.com/hashicorp/terraform/helper/schema" @@ -30,13 +35,14 @@ func dataSourceCloudstackInstance() *schema.Resource { return &schema.Resource{ Read: dataSourceCloudstackInstanceRead, Schema: map[string]*schema.Schema{ + "filter": dataSourceFiltersSchema(), + //Computed values "instance_id": { Type: schema.TypeString, - Required: true, + Computed: true, }, - // Computed values "account": { Type: schema.TypeString, Computed: true, @@ -62,10 +68,7 @@ func dataSourceCloudstackInstance() *schema.Resource { Type: schema.TypeString, Computed: true, }, - // "ip_address": { - // Type: schema.TypeString, - // Computed: true, - // }, + "tags": tagsSchema(), }, } } @@ -75,37 +78,96 @@ func dataSourceCloudstackInstanceRead(d *schema.ResourceData, meta interface{}) cs := meta.(*cloudstack.CloudStackClient) p := cs.VirtualMachine.NewListVirtualMachinesParams() - instance_id := d.Get("instance_id").(string) - - log.Printf("Instance ID =====================> %v", instance_id) - - csInstances, _ := cs.VirtualMachine.ListVirtualMachines(p) - - for _, instance := range csInstances.VirtualMachines { - log.Printf("Instance ===============> %v", instance) - if instance.Id == instance_id { - d.SetId(instance.Id) - d.Set("instance_id", instance.Id) - d.Set("account", instance.Account) - d.Set("created", instance.Created) - d.Set("display_name", instance.Displayname) - d.Set("state", instance.State) - d.Set("host_id", instance.Hostid) - d.Set("zone_id", instance.Zoneid) - - log.Printf("=========================================================================") - - log.Printf("Instance-ID being Set To: ===============> %v", d.Get("instance_id").(string)) - log.Printf("Instance-Created being Set To: ===============> %v", d.Get("created").(string)) - log.Printf("Instance-Display-Name being Set To: ===============> %v", d.Get("display_name").(string)) - log.Printf("Instance-State being Set To: ===============> %v", d.Get("state").(string)) - log.Printf("Instance-Host-ID being Set To: ===============> %v", d.Get("host_id").(string)) - log.Printf("Instance-Zone-ID being Set To: ===============> %v", d.Get("zone_id").(string)) - - log.Printf("=========================================================================") + + csInstances, err := cs.VirtualMachine.ListVirtualMachines(p) + if err != nil { + return fmt.Errorf("Failed to list instances: %s", err) + } + + filters := d.Get("filter") + var instances []*cloudstack.VirtualMachine + + for _, i := range csInstances.VirtualMachines { + match, err := applyInstanceFilters(i, filters.(*schema.Set)) + if err != nil { + return err + } + + if match { + instances = append(instances, i) } } - log.Printf("Instance Data Source Read Ended") + + if len(instances) == 0 { + return fmt.Errorf("No instance is matching with the specified regex") + } + + instance, err := latestInstance(instances) + if err != nil { + return err + } + log.Printf("[DEBUG] Selected instances: %s\n", instance.Displayname) + + return instanceDescriptionAttributes(d, instance) +} + +func instanceDescriptionAttributes(d *schema.ResourceData, instance *cloudstack.VirtualMachine) error { + d.SetId(instance.Id) + d.Set("instance_id", instance.Id) + d.Set("account", instance.Account) + d.Set("created", instance.Created) + d.Set("display_name", instance.Displayname) + d.Set("state", instance.State) + d.Set("host_id", instance.Hostid) + d.Set("zone_id", instance.Zoneid) + + tags := make(map[string]interface{}) + for _, tag := range instance.Tags { + tags[tag.Key] = tag.Value + } + d.Set("tags", tags) return nil } + +func latestInstance(instances []*cloudstack.VirtualMachine) (*cloudstack.VirtualMachine, error) { + var latest time.Time + var instance *cloudstack.VirtualMachine + + for _, i := range instances { + created, err := time.Parse("2006-01-02T15:04:05-0700", i.Created) + if err != nil { + return nil, fmt.Errorf("Failed to parse creation date of an instance: %s", err) + } + + if created.After(latest) { + latest = created + instance = i + } + } + + return instance, nil +} + +func applyInstanceFilters(instance *cloudstack.VirtualMachine, filters *schema.Set) (bool, error) { + var instanceJSON map[string]interface{} + i, _ := json.Marshal(instance) + err := json.Unmarshal(i, &instanceJSON) + if err != nil { + return false, err + } + for _, f := range filters.List() { + m := f.(map[string]interface{}) + r, err := regexp.Compile(m["value"].(string)) + if err != nil { + return false, fmt.Errorf("Invalid regex: %s", err) + } + updatedName := strings.ReplaceAll(m["name"].(string), "_", "") + instanceField := instanceJSON[updatedName].(string) + if !r.MatchString(instanceField) { + return false, nil + } + + } + return true, nil +} From 99ffd7108ef5f0431861cbf177545198be4fe282 Mon Sep 17 00:00:00 2001 From: damans227 Date: Mon, 6 Jun 2022 18:10:27 -0400 Subject: [PATCH 06/39] add ip-address to schema and allow filtering off of it --- cloudstack/data_source_cloudstack_instance.go | 47 ++++++++++++++++--- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/cloudstack/data_source_cloudstack_instance.go b/cloudstack/data_source_cloudstack_instance.go index f6699d94..0ba1301d 100644 --- a/cloudstack/data_source_cloudstack_instance.go +++ b/cloudstack/data_source_cloudstack_instance.go @@ -52,23 +52,43 @@ func dataSourceCloudstackInstance() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "state": { Type: schema.TypeString, Computed: true, }, + "host_id": { Type: schema.TypeString, Computed: true, }, + "zone_id": { Type: schema.TypeString, Computed: true, }, + "created": { Type: schema.TypeString, Computed: true, }, + "tags": tagsSchema(), + + "nic": { + Type: schema.TypeList, + Computed: true, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ip_address": { + Type: schema.TypeString, + Computed: true, + Optional: true, + }, + }, + }, + }, }, } } @@ -78,23 +98,34 @@ func dataSourceCloudstackInstanceRead(d *schema.ResourceData, meta interface{}) cs := meta.(*cloudstack.CloudStackClient) p := cs.VirtualMachine.NewListVirtualMachinesParams() - csInstances, err := cs.VirtualMachine.ListVirtualMachines(p) + if err != nil { return fmt.Errorf("Failed to list instances: %s", err) } filters := d.Get("filter") + nic := d.Get("nic").([]interface{}) var instances []*cloudstack.VirtualMachine - for _, i := range csInstances.VirtualMachines { - match, err := applyInstanceFilters(i, filters.(*schema.Set)) - if err != nil { - return err + if len(nic) != 0 { + ip_address := nic[0].(map[string]interface{})["ip_address"] + for _, i := range csInstances.VirtualMachines { + if ip_address == i.Nic[0].Ipaddress { + instances = append(instances, i) + } } - if match { - instances = append(instances, i) + } else { + for _, i := range csInstances.VirtualMachines { + match, err := applyInstanceFilters(i, filters.(*schema.Set)) + if err != nil { + return err + } + + if match { + instances = append(instances, i) + } } } @@ -120,6 +151,7 @@ func instanceDescriptionAttributes(d *schema.ResourceData, instance *cloudstack. d.Set("state", instance.State) d.Set("host_id", instance.Hostid) d.Set("zone_id", instance.Zoneid) + d.Set("nic", []interface{}{map[string]string{"ip_address": instance.Nic[0].Ipaddress}}) tags := make(map[string]interface{}) for _, tag := range instance.Tags { @@ -156,6 +188,7 @@ func applyInstanceFilters(instance *cloudstack.VirtualMachine, filters *schema.S if err != nil { return false, err } + for _, f := range filters.List() { m := f.(map[string]interface{}) r, err := regexp.Compile(m["value"].(string)) From 7ea2d6003359abd41799b6c6138ea2ea0d2e4269 Mon Sep 17 00:00:00 2001 From: damans227 Date: Wed, 8 Jun 2022 19:45:31 -0400 Subject: [PATCH 07/39] create a method stub to acceptance test the instance data-source --- .../data_source_cloudstack_instance_test.go | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 cloudstack/data_source_cloudstack_instance_test.go diff --git a/cloudstack/data_source_cloudstack_instance_test.go b/cloudstack/data_source_cloudstack_instance_test.go new file mode 100644 index 00000000..385d0a5b --- /dev/null +++ b/cloudstack/data_source_cloudstack_instance_test.go @@ -0,0 +1,66 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "testing" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccDataSourceCloudStackInstance_basic(t *testing.T) { + var instance cloudstack.VirtualMachine + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceCloudStackInstance_basic, + Check: resource.ComposeTestCheckFunc( + testAccDataSourceCheckCloudStackInstanceExists( + "cloudstack_instance.foobar", &instance), + testAccDataSourceCheckCloudStackInstanceAttributes(&instance), + resource.TestCheckResourceAttr( + "cloudstack_instance.foobar", "user_data", "0cf3dcdc356ec8369494cb3991985ecd5296cdd5"), + ), + }, + }, + }) +} + +const testAccDataSourceCloudStackInstance_basic = `` + +func testAccDataSourceCheckCloudStackInstanceExists( + n string, instance *cloudstack.VirtualMachine) resource.TestCheckFunc { + return func(s *terraform.State) error { + return nil + } +} + +func testAccDataSourceCheckCloudStackInstanceAttributes( + instance *cloudstack.VirtualMachine) resource.TestCheckFunc { + return func(s *terraform.State) error { + return nil + } +} From c4f18cc1e352df60f9e0067f9a25ff91532436cf Mon Sep 17 00:00:00 2001 From: damans227 Date: Fri, 10 Jun 2022 21:39:42 -0400 Subject: [PATCH 08/39] basic acceptance test for instance data-source passing --- .../data_source_cloudstack_instance_test.go | 52 +++++++++---------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/cloudstack/data_source_cloudstack_instance_test.go b/cloudstack/data_source_cloudstack_instance_test.go index 385d0a5b..0e2979cf 100644 --- a/cloudstack/data_source_cloudstack_instance_test.go +++ b/cloudstack/data_source_cloudstack_instance_test.go @@ -22,45 +22,43 @@ package cloudstack import ( "testing" - "github.com/apache/cloudstack-go/v2/cloudstack" "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/terraform" ) -func TestAccDataSourceCloudStackInstance_basic(t *testing.T) { - var instance cloudstack.VirtualMachine +func TestAccInstanceDataSource_basic(t *testing.T) { + resourceName := "cloudstack_instance.my_instance" + datasourceName := "data.cloudstack_instance.my_instance_test" resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckCloudStackInstanceDestroy, + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: testAccDataSourceCloudStackInstance_basic, + Config: testAccInstanceDataSourceConfig_basic, Check: resource.ComposeTestCheckFunc( - testAccDataSourceCheckCloudStackInstanceExists( - "cloudstack_instance.foobar", &instance), - testAccDataSourceCheckCloudStackInstanceAttributes(&instance), - resource.TestCheckResourceAttr( - "cloudstack_instance.foobar", "user_data", "0cf3dcdc356ec8369494cb3991985ecd5296cdd5"), + resource.TestCheckResourceAttrPair(datasourceName, "display_name", resourceName, "display_name"), ), + ExpectNonEmptyPlan: true, }, }, }) } -const testAccDataSourceCloudStackInstance_basic = `` - -func testAccDataSourceCheckCloudStackInstanceExists( - n string, instance *cloudstack.VirtualMachine) resource.TestCheckFunc { - return func(s *terraform.State) error { - return nil +const testAccInstanceDataSourceConfig_basic = ` + resource "cloudstack_instance" "my_instance" { + name = "server-a" + service_offering = "Small Instance" + network_id = "b9c953a0-8686-4240-b8a4-43849f7079ff" + template = "CentOS 5.5(64-bit) no GUI (KVM)" + zone = "DC" + } + data "cloudstack_instance" "my_instance_test" { + filter { + name = "display_name" + value = "server-a" + } + depends_on = [ + cloudstack_instance.my_instance + ] } -} - -func testAccDataSourceCheckCloudStackInstanceAttributes( - instance *cloudstack.VirtualMachine) resource.TestCheckFunc { - return func(s *terraform.State) error { - return nil - } -} +` From 0244431c56ac46cb31fc378cca55d4990b30ccec Mon Sep 17 00:00:00 2001 From: damans227 Date: Sun, 12 Jun 2022 18:14:21 -0400 Subject: [PATCH 09/39] adding comments to improve the readability of code --- cloudstack/data_source_cloudstack_instance.go | 6 ++++-- cloudstack/data_source_cloudstack_instance_test.go | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/cloudstack/data_source_cloudstack_instance.go b/cloudstack/data_source_cloudstack_instance.go index 0ba1301d..a78f39b1 100644 --- a/cloudstack/data_source_cloudstack_instance.go +++ b/cloudstack/data_source_cloudstack_instance.go @@ -108,6 +108,8 @@ func dataSourceCloudstackInstanceRead(d *schema.ResourceData, meta interface{}) nic := d.Get("nic").([]interface{}) var instances []*cloudstack.VirtualMachine + //the if-else block to check whether to filter the data source by an IP address + // or by any other exported attributes if len(nic) != 0 { ip_address := nic[0].(map[string]interface{})["ip_address"] for _, i := range csInstances.VirtualMachines { @@ -115,7 +117,6 @@ func dataSourceCloudstackInstanceRead(d *schema.ResourceData, meta interface{}) instances = append(instances, i) } } - } else { for _, i := range csInstances.VirtualMachines { match, err := applyInstanceFilters(i, filters.(*schema.Set)) @@ -132,7 +133,8 @@ func dataSourceCloudstackInstanceRead(d *schema.ResourceData, meta interface{}) if len(instances) == 0 { return fmt.Errorf("No instance is matching with the specified regex") } - + //return the latest instance from the list of filtered instances according + //to its creation date instance, err := latestInstance(instances) if err != nil { return err diff --git a/cloudstack/data_source_cloudstack_instance_test.go b/cloudstack/data_source_cloudstack_instance_test.go index 0e2979cf..e27b1e14 100644 --- a/cloudstack/data_source_cloudstack_instance_test.go +++ b/cloudstack/data_source_cloudstack_instance_test.go @@ -25,6 +25,8 @@ import ( "github.com/hashicorp/terraform/helper/resource" ) +//basic acceptance to check if the display_name attribute has same value in +//the created instance and its data source respectively. func TestAccInstanceDataSource_basic(t *testing.T) { resourceName := "cloudstack_instance.my_instance" datasourceName := "data.cloudstack_instance.my_instance_test" From 49a279d02a1953c3c7239a78eb1a6159db564c73 Mon Sep 17 00:00:00 2001 From: damans227 Date: Tue, 14 Jun 2022 17:42:19 -0400 Subject: [PATCH 10/39] add basic acceptance test for ssh keypair data-source --- ...data_source_cloudstack_ssh_keypair_test.go | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 cloudstack/data_source_cloudstack_ssh_keypair_test.go diff --git a/cloudstack/data_source_cloudstack_ssh_keypair_test.go b/cloudstack/data_source_cloudstack_ssh_keypair_test.go new file mode 100644 index 00000000..32bd3fd0 --- /dev/null +++ b/cloudstack/data_source_cloudstack_ssh_keypair_test.go @@ -0,0 +1,62 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccSshKeyPairDataSource_basic(t *testing.T) { + resourceName := "cloudstack_ssh_keypair.ssh-keypair-resource" + datasourceName := "data.cloudstack_ssh_keypair.ssh-keypair-data" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccSshKeyPairDataSourceConfig_basic, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(datasourceName, "id", resourceName, "id"), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +const testAccSshKeyPairDataSourceConfig_basic = ` + resource "cloudstack_ssh_keypair" "ssh-keypair-resource"{ + name = "myKey" + } + + data "cloudstack_ssh_keypair" "ssh-keypair-data" { + filter { + name = "name" + value = "myKey" + } + depends_on = [ + cloudstack_ssh_keypair.ssh-keypair-resource + ] + + } + ` From 2cff5eb92692312e4df630d5b4868d4d40e1b126 Mon Sep 17 00:00:00 2001 From: damans227 Date: Fri, 17 Jun 2022 18:53:23 -0400 Subject: [PATCH 11/39] create network offering resource --- cloudstack/provider.go | 1 + .../resource_cloudstack_network_offering.go | 94 +++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 cloudstack/resource_cloudstack_network_offering.go diff --git a/cloudstack/provider.go b/cloudstack/provider.go index 2df64a30..8d787e2f 100644 --- a/cloudstack/provider.go +++ b/cloudstack/provider.go @@ -109,6 +109,7 @@ func Provider() terraform.ResourceProvider { "cloudstack_vpn_connection": resourceCloudStackVPNConnection(), "cloudstack_vpn_customer_gateway": resourceCloudStackVPNCustomerGateway(), "cloudstack_vpn_gateway": resourceCloudStackVPNGateway(), + "cloudstack_network_offering": resourceCloudStackNetworkOffering(), }, ConfigureFunc: providerConfigure, diff --git a/cloudstack/resource_cloudstack_network_offering.go b/cloudstack/resource_cloudstack_network_offering.go new file mode 100644 index 00000000..d0cb2cb9 --- /dev/null +++ b/cloudstack/resource_cloudstack_network_offering.go @@ -0,0 +1,94 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "log" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceCloudStackNetworkOffering() *schema.Resource { + return &schema.Resource{ + Create: resourceCloudStackNetworkOfferingCreate, + Read: resourceCloudStackNetworkOfferingRead, + Update: resourceCloudStackNetworkOfferingUpdate, + Delete: resourceCloudStackNetworkOfferingDelete, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "display_text": { + Type: schema.TypeString, + Required: true, + }, + "guest_ip_type": { + Type: schema.TypeString, + Required: true, + }, + "traffic_type": { + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +func resourceCloudStackNetworkOfferingCreate(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + name := d.Get("name").(string) + display_text := d.Get("display_text").(string) + guest_ip_type := d.Get("guest_ip_type").(string) + traffic_type := d.Get("traffic_type").(string) + + // Create a new parameter struct + p := cs.NetworkOffering.NewCreateNetworkOfferingParams(display_text, guest_ip_type, name, []string{}, traffic_type) + + if guest_ip_type == "Shared" { + p.SetSpecifyvlan(true) + p.SetSpecifyipranges(true) + } + + log.Printf("[DEBUG] Creating Network Offering %s", name) + n, err := cs.NetworkOffering.CreateNetworkOffering(p) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Network Offering %s successfully created", name) + d.SetId(n.Id) + + return resourceCloudStackNetworkOfferingRead(d, meta) +} + +func resourceCloudStackNetworkOfferingUpdate(d *schema.ResourceData, meta interface{}) error { + return nil +} + +func resourceCloudStackNetworkOfferingDelete(d *schema.ResourceData, meta interface{}) error { + return nil +} + +func resourceCloudStackNetworkOfferingRead(d *schema.ResourceData, meta interface{}) error { + return nil +} From f4d6d3e7c7483de40312d3929a40e9d632e53546 Mon Sep 17 00:00:00 2001 From: damans227 Date: Mon, 20 Jun 2022 20:25:02 -0400 Subject: [PATCH 12/39] complete network offering resource type --- .../resource_cloudstack_network_offering.go | 122 +++++++++++++++++- 1 file changed, 121 insertions(+), 1 deletion(-) diff --git a/cloudstack/resource_cloudstack_network_offering.go b/cloudstack/resource_cloudstack_network_offering.go index d0cb2cb9..dd17d96b 100644 --- a/cloudstack/resource_cloudstack_network_offering.go +++ b/cloudstack/resource_cloudstack_network_offering.go @@ -20,6 +20,7 @@ package cloudstack import ( + "fmt" "log" "github.com/apache/cloudstack-go/v2/cloudstack" @@ -82,13 +83,132 @@ func resourceCloudStackNetworkOfferingCreate(d *schema.ResourceData, meta interf } func resourceCloudStackNetworkOfferingUpdate(d *schema.ResourceData, meta interface{}) error { - return nil + cs := meta.(*cloudstack.CloudStackClient) + d.Partial(true) + + name := d.Get("name").(string) + + // Check if the name is changed and if so, update the network offering + if d.HasChange("name") { + log.Printf("[DEBUG] Name changed for %s, starting update", name) + + // Create a new parameter struct + p := cs.NetworkOffering.NewUpdateNetworkOfferingParams() + + // Set the new name + p.SetName(d.Get("name").(string)) + + // Update the name + _, err := cs.NetworkOffering.UpdateNetworkOffering(p) + if err != nil { + return fmt.Errorf( + "Error updating the name for network offering %s: %s", name, err) + } + + d.SetPartial("name") + } + + // Check if the display text is changed and if so, update the virtual machine + if d.HasChange("display_text") { + log.Printf("[DEBUG] Display text changed for %s, starting update", name) + + // Create a new parameter struct + p := cs.NetworkOffering.NewUpdateNetworkOfferingParams() + + // Set the new display text + p.SetName(d.Get("display_text").(string)) + + // Update the display text + _, err := cs.NetworkOffering.UpdateNetworkOffering(p) + if err != nil { + return fmt.Errorf( + "Error updating the display text for network offering %s: %s", name, err) + } + + d.SetPartial("display_text") + } + + // Check if the guest ip type is changed and if so, update the virtual machine + if d.HasChange("guest_ip_type") { + log.Printf("[DEBUG] Guest ip type changed for %s, starting update", name) + + // Create a new parameter struct + p := cs.NetworkOffering.NewUpdateNetworkOfferingParams() + + // Set the new uest ip type + p.SetName(d.Get("guest_ip_type").(string)) + + // Update the uest ip type + _, err := cs.NetworkOffering.UpdateNetworkOffering(p) + if err != nil { + return fmt.Errorf( + "Error updating the guest ip type for network offering %s: %s", name, err) + } + + d.SetPartial("guest_ip_type") + } + + // Check if the traffic type is changed and if so, update the virtual machine + if d.HasChange("traffic_type") { + log.Printf("[DEBUG] Traffic type changed for %s, starting update", name) + + // Create a new parameter struct + p := cs.NetworkOffering.NewUpdateNetworkOfferingParams() + + // Set the new traffic type + p.SetName(d.Get("traffic_type").(string)) + + // Update the traffic type + _, err := cs.NetworkOffering.UpdateNetworkOffering(p) + if err != nil { + return fmt.Errorf( + "Error updating the traffic type for network offering %s: %s", name, err) + } + + d.SetPartial("traffic_type") + } + + d.Partial(false) + + return resourceCloudStackInstanceRead(d, meta) } func resourceCloudStackNetworkOfferingDelete(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + // Create a new parameter struct + p := cs.NetworkOffering.NewDeleteNetworkOfferingParams(d.Id()) + _, err := cs.NetworkOffering.DeleteNetworkOffering(p) + + if err != nil { + return fmt.Errorf("Error deleting Kubernetes Cluster: %s", err) + } + return nil } func resourceCloudStackNetworkOfferingRead(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + log.Printf("[DEBUG] Retrieving Network Offering %s", d.Get("name").(string)) + + // Get the Network Offering details + n, count, err := cs.NetworkOffering.GetNetworkOfferingByName(d.Get("name").(string)) + + if err != nil { + if count == 0 { + log.Printf("[DEBUG] Network Offering %s does no longer exist", d.Get("name").(string)) + d.SetId("") + return nil + } + + return err + } + + d.SetId(n.Id) + d.Set("name", n.Name) + d.Set("display_text", n.Displaytext) + d.Set("guest_ip_type", n.Guestiptype) + d.Set("traffic_type", n.Traffictype) + return nil } From 10d2b8b0098147bfdedc78964bd7f52a77c9e7a5 Mon Sep 17 00:00:00 2001 From: damans227 Date: Tue, 21 Jun 2022 19:20:30 -0400 Subject: [PATCH 13/39] create network offering data source --- ...data_source_cloudstack_network_offering.go | 148 ++++++++++++++++++ cloudstack/provider.go | 7 +- 2 files changed, 152 insertions(+), 3 deletions(-) create mode 100644 cloudstack/data_source_cloudstack_network_offering.go diff --git a/cloudstack/data_source_cloudstack_network_offering.go b/cloudstack/data_source_cloudstack_network_offering.go new file mode 100644 index 00000000..d1354425 --- /dev/null +++ b/cloudstack/data_source_cloudstack_network_offering.go @@ -0,0 +1,148 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "encoding/json" + "fmt" + "log" + "regexp" + "strings" + "time" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceCloudstackNetworkOffering() *schema.Resource { + return &schema.Resource{ + Read: datasourceCloudStackNetworkOfferingRead, + Schema: map[string]*schema.Schema{ + "filter": dataSourceFiltersSchema(), + + //Computed values + "name": { + Type: schema.TypeString, + Computed: true, + }, + "display_text": { + Type: schema.TypeString, + Computed: true, + }, + "guest_ip_type": { + Type: schema.TypeString, + Computed: true, + }, + "traffic_type": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func datasourceCloudStackNetworkOfferingRead(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + p := cs.NetworkOffering.NewListNetworkOfferingsParams() + csNetworkOfferings, err := cs.NetworkOffering.ListNetworkOfferings(p) + + if err != nil { + return fmt.Errorf("Failed to list network offerings: %s", err) + } + + filters := d.Get("filter") + var networkOfferings []*cloudstack.NetworkOffering + + for _, n := range csNetworkOfferings.NetworkOfferings { + match, err := applyNetworkOfferingFilters(n, filters.(*schema.Set)) + if err != nil { + return err + } + if match { + networkOfferings = append(networkOfferings, n) + } + } + + if len(networkOfferings) == 0 { + return fmt.Errorf("No network offering is matching with the specified regex") + } + //return the latest instance from the list of filtered instances according + //to its creation date + networkOffering, err := latestNetworkOffering(networkOfferings) + if err != nil { + return err + } + log.Printf("[DEBUG] Selected network offerings: %s\n", networkOffering.Displaytext) + + return networkOfferingDescriptionAttributes(d, networkOffering) +} + +func networkOfferingDescriptionAttributes(d *schema.ResourceData, networkOffering *cloudstack.NetworkOffering) error { + d.SetId(networkOffering.Id) + d.Set("name", networkOffering.Name) + d.Set("display_text", networkOffering.Displaytext) + d.Set("guest_ip_type", networkOffering.Guestiptype) + d.Set("traffic_type", networkOffering.Traffictype) + + return nil +} + +func latestNetworkOffering(networkOfferings []*cloudstack.NetworkOffering) (*cloudstack.NetworkOffering, error) { + var latest time.Time + var networkOffering *cloudstack.NetworkOffering + + for _, n := range networkOfferings { + created, err := time.Parse("2006-01-02T15:04:05-0700", n.Created) + if err != nil { + return nil, fmt.Errorf("Failed to parse creation date of an instance: %s", err) + } + + if created.After(latest) { + latest = created + networkOffering = n + } + } + + return networkOffering, nil +} + +func applyNetworkOfferingFilters(networkOffering *cloudstack.NetworkOffering, filters *schema.Set) (bool, error) { + var networkOfferingJSON map[string]interface{} + k, _ := json.Marshal(networkOffering) + err := json.Unmarshal(k, &networkOfferingJSON) + if err != nil { + return false, err + } + + for _, f := range filters.List() { + m := f.(map[string]interface{}) + r, err := regexp.Compile(m["value"].(string)) + if err != nil { + return false, fmt.Errorf("Invalid regex: %s", err) + } + updatedName := strings.ReplaceAll(m["name"].(string), "_", "") + sshKeyPairField := networkOfferingJSON[updatedName].(string) + if !r.MatchString(sshKeyPairField) { + return false, nil + } + + } + return true, nil +} diff --git a/cloudstack/provider.go b/cloudstack/provider.go index 8d787e2f..3ba84ff5 100644 --- a/cloudstack/provider.go +++ b/cloudstack/provider.go @@ -78,9 +78,10 @@ func Provider() terraform.ResourceProvider { }, DataSourcesMap: map[string]*schema.Resource{ - "cloudstack_template": dataSourceCloudstackTemplate(), - "cloudstack_ssh_keypair": dataSourceCloudstackSSHKeyPair(), - "cloudstack_instance": dataSourceCloudstackInstance(), + "cloudstack_template": dataSourceCloudstackTemplate(), + "cloudstack_ssh_keypair": dataSourceCloudstackSSHKeyPair(), + "cloudstack_instance": dataSourceCloudstackInstance(), + "cloudstack_network_offering": dataSourceCloudstackNetworkOffering(), }, ResourcesMap: map[string]*schema.Resource{ From fcc5d600ebda720dd69b96b585772c3fbcbee99c Mon Sep 17 00:00:00 2001 From: damans227 Date: Tue, 21 Jun 2022 19:27:05 -0400 Subject: [PATCH 14/39] add basic acceptance test for network offering data source --- ...source_cloudstack_network_offering_test.go | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 cloudstack/data_source_cloudstack_network_offering_test.go diff --git a/cloudstack/data_source_cloudstack_network_offering_test.go b/cloudstack/data_source_cloudstack_network_offering_test.go new file mode 100644 index 00000000..0101b318 --- /dev/null +++ b/cloudstack/data_source_cloudstack_network_offering_test.go @@ -0,0 +1,65 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccNetworkOfferingDataSource_basic(t *testing.T) { + resourceName := "cloudstack_network_offering.net-off-resource" + datasourceName := "data.cloudstack_network_offering.net-off-data-source" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testNetworkOfferingDataSourceConfig_basic, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(datasourceName, "name", resourceName, "name"), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +const testNetworkOfferingDataSourceConfig_basic = ` +resource "cloudstack_network_offering" "net-off-resource"{ + name = "TestNetworkDisplay01" + display_text = "TestNetworkDisplay01" + guest_ip_type = "Isolated" + traffic_type = "Guest" + } + + data "cloudstack_network_offering" "net-off-data-source"{ + + filter{ + name = "name" + value="TestNetworkDisplay01" + } + depends_on = [ + cloudstack_network_offering.net-off-resource + ] + } + ` From 2a36da546f4de550a38b725b0e3d60144e6ee6ff Mon Sep 17 00:00:00 2001 From: damans227 Date: Mon, 27 Jun 2022 19:22:03 -0400 Subject: [PATCH 15/39] create disk offering resource --- .../resource_cloudstack_disk_offering.go | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 cloudstack/resource_cloudstack_disk_offering.go diff --git a/cloudstack/resource_cloudstack_disk_offering.go b/cloudstack/resource_cloudstack_disk_offering.go new file mode 100644 index 00000000..5b4e8c31 --- /dev/null +++ b/cloudstack/resource_cloudstack_disk_offering.go @@ -0,0 +1,53 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import "github.com/hashicorp/terraform/helper/schema" + +func resourceCloudstackDiskOffering() *schema.Resource { + return &schema.Resource{ + Create: resourceCloudStackDiskOfferingCreate, + Read: resourceCloudStackDiskOfferingRead, + Update: resourceCloudStackDiskOfferingUpdate, + Delete: resourceCloudStackDiskOfferingDelete, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "display_text": { + Type: schema.TypeString, + Required: true, + }, + "disk_size": { + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +func resourceCloudStackDiskOfferingCreate(d *schema.ResourceData, meta interface{}) error { return nil } + +func resourceCloudStackDiskOfferingRead(d *schema.ResourceData, meta interface{}) error { return nil } + +func resourceCloudStackDiskOfferingUpdate(d *schema.ResourceData, meta interface{}) error { return nil } + +func resourceCloudStackDiskOfferingDelete(d *schema.ResourceData, meta interface{}) error { return nil } From 420c5f996110d16232b00345b67e963c383f24d2 Mon Sep 17 00:00:00 2001 From: damans227 Date: Mon, 27 Jun 2022 20:52:55 -0400 Subject: [PATCH 16/39] implement create method for disk offering resource --- cloudstack/provider.go | 1 + .../resource_cloudstack_disk_offering.go | 34 ++++++++++++++++--- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/cloudstack/provider.go b/cloudstack/provider.go index 3ba84ff5..cb2e01fc 100644 --- a/cloudstack/provider.go +++ b/cloudstack/provider.go @@ -111,6 +111,7 @@ func Provider() terraform.ResourceProvider { "cloudstack_vpn_customer_gateway": resourceCloudStackVPNCustomerGateway(), "cloudstack_vpn_gateway": resourceCloudStackVPNGateway(), "cloudstack_network_offering": resourceCloudStackNetworkOffering(), + "cloudstack_disk_offering": resourceCloudStackDiskOffering(), }, ConfigureFunc: providerConfigure, diff --git a/cloudstack/resource_cloudstack_disk_offering.go b/cloudstack/resource_cloudstack_disk_offering.go index 5b4e8c31..daa6b889 100644 --- a/cloudstack/resource_cloudstack_disk_offering.go +++ b/cloudstack/resource_cloudstack_disk_offering.go @@ -19,9 +19,14 @@ package cloudstack -import "github.com/hashicorp/terraform/helper/schema" +import ( + "log" -func resourceCloudstackDiskOffering() *schema.Resource { + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceCloudStackDiskOffering() *schema.Resource { return &schema.Resource{ Create: resourceCloudStackDiskOfferingCreate, Read: resourceCloudStackDiskOfferingRead, @@ -37,14 +42,35 @@ func resourceCloudstackDiskOffering() *schema.Resource { Required: true, }, "disk_size": { - Type: schema.TypeString, + Type: schema.TypeInt, Required: true, }, }, } } -func resourceCloudStackDiskOfferingCreate(d *schema.ResourceData, meta interface{}) error { return nil } +func resourceCloudStackDiskOfferingCreate(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + name := d.Get("name").(string) + display_text := d.Get("display_text").(string) + disk_size := d.Get("disk_size").(int) + + // Create a new parameter struct + p := cs.DiskOffering.NewCreateDiskOfferingParams(name, display_text) + p.SetDisksize(int64(disk_size)) + + log.Printf("[DEBUG] Creating Disk Offering %s", name) + diskOff, err := cs.DiskOffering.CreateDiskOffering(p) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Disk Offering %s successfully created", name) + d.SetId(diskOff.Id) + + return resourceCloudStackDiskOfferingRead(d, meta) +} func resourceCloudStackDiskOfferingRead(d *schema.ResourceData, meta interface{}) error { return nil } From 14585017dda5c52ffb1d88e0e7f22f848d092710 Mon Sep 17 00:00:00 2001 From: damans227 Date: Tue, 28 Jun 2022 18:15:54 -0400 Subject: [PATCH 17/39] create cloudstack volume resource type --- cloudstack/provider.go | 1 + cloudstack/resource_cloudstack_volume.go | 115 +++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 cloudstack/resource_cloudstack_volume.go diff --git a/cloudstack/provider.go b/cloudstack/provider.go index cb2e01fc..5b5f2def 100644 --- a/cloudstack/provider.go +++ b/cloudstack/provider.go @@ -112,6 +112,7 @@ func Provider() terraform.ResourceProvider { "cloudstack_vpn_gateway": resourceCloudStackVPNGateway(), "cloudstack_network_offering": resourceCloudStackNetworkOffering(), "cloudstack_disk_offering": resourceCloudStackDiskOffering(), + "cloudstack_volume": resourceCloudStackVolume(), }, ConfigureFunc: providerConfigure, diff --git a/cloudstack/resource_cloudstack_volume.go b/cloudstack/resource_cloudstack_volume.go new file mode 100644 index 00000000..a2fb725d --- /dev/null +++ b/cloudstack/resource_cloudstack_volume.go @@ -0,0 +1,115 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "fmt" + "log" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceCloudStackVolume() *schema.Resource { + return &schema.Resource{ + Create: resourceCloudStackVolumeCreate, + Read: resourceCloudStackVolumeRead, + Delete: resourceCloudStackVolumeDelete, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "disk_offering_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "zone_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceCloudStackVolumeCreate(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + name := d.Get("name").(string) + disk_offering_id := d.Get("disk_offering_id").(string) + zone_id := d.Get("zone_id").(string) + + //Create a new parameter struct + p := cs.Volume.NewCreateVolumeParams() + p.SetDiskofferingid(disk_offering_id) + p.SetZoneid(zone_id) + p.SetName(name) + + log.Printf("[DEBUG] Creating Volume %s", name) + v, err := cs.Volume.CreateVolume(p) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Volume %s successfully created", name) + d.SetId(v.Id) + + return resourceCloudStackVolumeRead(d, meta) +} +func resourceCloudStackVolumeRead(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + log.Printf("[DEBUG] Retrieving Volume %s", d.Get("name").(string)) + + // Get the Volume details + v, count, err := cs.Volume.GetVolumeByName(d.Get("name").(string)) + + if err != nil { + if count == 0 { + log.Printf("[DEBUG] Volume %s does no longer exist", d.Get("name").(string)) + d.SetId("") + return nil + } + return err + } + + d.SetId(v.Id) + d.Set("name", v.Name) + d.Set("disk_offering_id", v.Diskofferingid) + d.Set("zone_id", v.Zoneid) + + return nil +} + +func resourceCloudStackVolumeDelete(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + // Create a new parameter struct + p := cs.Volume.NewDeleteVolumeParams(d.Id()) + _, err := cs.Volume.DeleteVolume(p) + + if err != nil { + return fmt.Errorf("Error deleting Volume: %s", err) + } + + return nil +} From 68600340f544f17a5bf92ad9ddd7a60a12bd39af Mon Sep 17 00:00:00 2001 From: damans227 Date: Wed, 29 Jun 2022 18:06:26 -0400 Subject: [PATCH 18/39] create cloudstack zone resource type --- cloudstack/provider.go | 1 + cloudstack/resource_cloudstack_zone.go | 119 +++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 cloudstack/resource_cloudstack_zone.go diff --git a/cloudstack/provider.go b/cloudstack/provider.go index 5b5f2def..81fb920c 100644 --- a/cloudstack/provider.go +++ b/cloudstack/provider.go @@ -113,6 +113,7 @@ func Provider() terraform.ResourceProvider { "cloudstack_network_offering": resourceCloudStackNetworkOffering(), "cloudstack_disk_offering": resourceCloudStackDiskOffering(), "cloudstack_volume": resourceCloudStackVolume(), + "cloudstack_zone": resourceCloudStackZone(), }, ConfigureFunc: providerConfigure, diff --git a/cloudstack/resource_cloudstack_zone.go b/cloudstack/resource_cloudstack_zone.go new file mode 100644 index 00000000..a259f656 --- /dev/null +++ b/cloudstack/resource_cloudstack_zone.go @@ -0,0 +1,119 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "fmt" + "log" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceCloudStackZone() *schema.Resource { + return &schema.Resource{ + Create: resourceCloudStackZoneCreate, + Read: resourceCloudStackZoneRead, + Update: resourceCloudStackZoneUpdate, + Delete: resourceCloudStackZoneDelete, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "dns1": { + Type: schema.TypeString, + Required: true, + }, + "internal_dns1": { + Type: schema.TypeString, + Required: true, + }, + "network_type": { + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +func resourceCloudStackZoneCreate(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + name := d.Get("name").(string) + dns1 := d.Get("dns1").(string) + internal_dns1 := d.Get("internal_dns1").(string) + network_type := d.Get("network_type").(string) + + // Create a new parameter struct + p := cs.Zone.NewCreateZoneParams(dns1, internal_dns1, name, network_type) + + log.Printf("[DEBUG] Creating Zone %s", name) + n, err := cs.Zone.CreateZone(p) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Zone %s successfully created", name) + d.SetId(n.Id) + + return resourceCloudStackZoneRead(d, meta) +} + +func resourceCloudStackZoneRead(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + log.Printf("[DEBUG] Retrieving Zone %s", d.Get("name").(string)) + + // Get the Zone details + z, count, err := cs.Zone.GetZoneByName(d.Get("name").(string)) + + if err != nil { + if count == 0 { + log.Printf("[DEBUG] Zone %s does no longer exist", d.Get("name").(string)) + d.SetId("") + return nil + } + return err + } + + d.SetId(z.Id) + d.Set("name", z.Name) + d.Set("dns1", z.Dns1) + d.Set("internal_dns1", z.Internaldns1) + d.Set("network_type", z.Networktype) + + return nil +} + +func resourceCloudStackZoneUpdate(d *schema.ResourceData, meta interface{}) error { return nil } + +func resourceCloudStackZoneDelete(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + // Create a new parameter struct + p := cs.Zone.NewDeleteZoneParams(d.Id()) + _, err := cs.Zone.DeleteZone(p) + + if err != nil { + return fmt.Errorf("Error deleting Zone: %s", err) + } + + return nil +} From 4b06faf25fdf0e2ad3843c184d2ce186fc3dbdea Mon Sep 17 00:00:00 2001 From: damans227 Date: Wed, 6 Jul 2022 20:19:55 -0400 Subject: [PATCH 19/39] create zone data source --- cloudstack/data_source_cloudstack_zone.go | 121 ++++++++++++++++++++++ cloudstack/provider.go | 1 + 2 files changed, 122 insertions(+) create mode 100644 cloudstack/data_source_cloudstack_zone.go diff --git a/cloudstack/data_source_cloudstack_zone.go b/cloudstack/data_source_cloudstack_zone.go new file mode 100644 index 00000000..39c8317c --- /dev/null +++ b/cloudstack/data_source_cloudstack_zone.go @@ -0,0 +1,121 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "encoding/json" + "fmt" + "log" + "regexp" + "strings" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceCloudStackZone() *schema.Resource { + return &schema.Resource{ + Read: dataSourceCloudstackZoneRead, + Schema: map[string]*schema.Schema{ + "filter": dataSourceFiltersSchema(), + + //Computed values + "name": { + Type: schema.TypeString, + Computed: true, + }, + "dns1": { + Type: schema.TypeString, + Computed: true, + }, + "internal_dns1": { + Type: schema.TypeString, + Computed: true, + }, + "network_type": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceCloudstackZoneRead(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + p := cs.Zone.NewListZonesParams() + csZones, err := cs.Zone.ListZones(p) + + if err != nil { + return fmt.Errorf("Failed to list zones: %s", err) + } + filters := d.Get("filter") + var zone *cloudstack.Zone + + for _, z := range csZones.Zones { + match, err := applyZoneFilters(z, filters.(*schema.Set)) + if err != nil { + return err + } + if match { + zone = z + } + } + + if zone == nil { + return fmt.Errorf("No zone is matching with the specified regex") + } + log.Printf("[DEBUG] Selected zone: %s\n", zone.Name) + + return zoneDescriptionAttributes(d, zone) +} + +func zoneDescriptionAttributes(d *schema.ResourceData, zone *cloudstack.Zone) error { + d.SetId(zone.Name) + d.Set("name", zone.Name) + d.Set("dns1", zone.Dns1) + d.Set("internal_dns1", zone.Internaldns1) + d.Set("network_type", zone.Networktype) + + return nil +} + +func applyZoneFilters(zone *cloudstack.Zone, filters *schema.Set) (bool, error) { + var zoneJSON map[string]interface{} + k, _ := json.Marshal(zone) + err := json.Unmarshal(k, &zoneJSON) + if err != nil { + return false, err + } + + for _, f := range filters.List() { + m := f.(map[string]interface{}) + r, err := regexp.Compile(m["value"].(string)) + if err != nil { + return false, fmt.Errorf("Invalid regex: %s", err) + } + updatedName := strings.ReplaceAll(m["name"].(string), "_", "") + zoneField := zoneJSON[updatedName].(string) + if !r.MatchString(zoneField) { + return false, nil + } + + } + return true, nil +} diff --git a/cloudstack/provider.go b/cloudstack/provider.go index 81fb920c..a382c7e6 100644 --- a/cloudstack/provider.go +++ b/cloudstack/provider.go @@ -82,6 +82,7 @@ func Provider() terraform.ResourceProvider { "cloudstack_ssh_keypair": dataSourceCloudstackSSHKeyPair(), "cloudstack_instance": dataSourceCloudstackInstance(), "cloudstack_network_offering": dataSourceCloudstackNetworkOffering(), + "cloudstack_zone": dataSourceCloudStackZone(), }, ResourcesMap: map[string]*schema.Resource{ From 165ea84e32c9a09479bd695075ad1415ee23788b Mon Sep 17 00:00:00 2001 From: damans227 Date: Mon, 11 Jul 2022 17:52:16 -0400 Subject: [PATCH 20/39] add acceptance test for zone datasource --- .../data_source_cloudstack_zone_test.go | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 cloudstack/data_source_cloudstack_zone_test.go diff --git a/cloudstack/data_source_cloudstack_zone_test.go b/cloudstack/data_source_cloudstack_zone_test.go new file mode 100644 index 00000000..b02bb48b --- /dev/null +++ b/cloudstack/data_source_cloudstack_zone_test.go @@ -0,0 +1,66 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccZoneDataSource_basic(t *testing.T) { + resourceName := "cloudstack_zone.zone-resource" + datasourceName := "data.cloudstack_zone.zone-data-source" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testZoneDataSourceConfig_basic, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(datasourceName, "name", resourceName, "name"), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +const testZoneDataSourceConfig_basic = ` +resource "cloudstack_zone" "zone-resource"{ + name = "TestZone" + dns1 = "8.8.8.8" + internal_dns1 = "172.20.0.1" + network_type = "Advanced" + } + + data "cloudstack_zone" "zone-data-source"{ + + filter{ + name = "name" + value="TestZone" + } + depends_on = [ + cloudstack_zone.zone-resource + ] + + } + ` From 5401174ebf4a70511667c083bb94243a5aee2392 Mon Sep 17 00:00:00 2001 From: damans227 Date: Wed, 13 Jul 2022 20:41:29 -0400 Subject: [PATCH 21/39] create service offering resource --- cloudstack/provider.go | 1 + .../resource_cloudstack_service_offering.go | 156 ++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 cloudstack/resource_cloudstack_service_offering.go diff --git a/cloudstack/provider.go b/cloudstack/provider.go index a382c7e6..553c4248 100644 --- a/cloudstack/provider.go +++ b/cloudstack/provider.go @@ -115,6 +115,7 @@ func Provider() terraform.ResourceProvider { "cloudstack_disk_offering": resourceCloudStackDiskOffering(), "cloudstack_volume": resourceCloudStackVolume(), "cloudstack_zone": resourceCloudStackZone(), + "cloudstack_service_offering": resourceCloudStackServiceOffering(), }, ConfigureFunc: providerConfigure, diff --git a/cloudstack/resource_cloudstack_service_offering.go b/cloudstack/resource_cloudstack_service_offering.go new file mode 100644 index 00000000..7916ceef --- /dev/null +++ b/cloudstack/resource_cloudstack_service_offering.go @@ -0,0 +1,156 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "fmt" + "log" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceCloudStackServiceOffering() *schema.Resource { + return &schema.Resource{ + Create: resourceCloudStackServiceOfferingCreate, + Read: resourceCloudStackServiceOfferingRead, + Update: resourceCloudStackServiceOfferingUpdate, + Delete: resourceCloudStackServiceOfferingDelete, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "display_name": { + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +func resourceCloudStackServiceOfferingCreate(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + name := d.Get("name").(string) + display_name := d.Get("display_name").(string) + + // Create a new parameter struct + p := cs.ServiceOffering.NewCreateServiceOfferingParams(display_name, name) + + log.Printf("[DEBUG] Creating Service Offering %s", name) + s, err := cs.ServiceOffering.CreateServiceOffering(p) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Service Offering %s successfully created", name) + d.SetId(s.Id) + + return resourceCloudStackServiceOfferingRead(d, meta) +} + +func resourceCloudStackServiceOfferingRead(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + log.Printf("[DEBUG] Retrieving Service Offering %s", d.Get("name").(string)) + + // Get the Service Offering details + s, count, err := cs.ServiceOffering.GetServiceOfferingByName(d.Get("name").(string)) + + if err != nil { + if count == 0 { + log.Printf("[DEBUG] Service Offering %s does no longer exist", d.Get("name").(string)) + d.SetId("") + return nil + } + return err + } + + d.SetId(s.Id) + d.Set("name", s.Name) + d.Set("display_name", s.Displaytext) + + return nil +} + +func resourceCloudStackServiceOfferingUpdate(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + d.Partial(true) + + name := d.Get("name").(string) + + // Check if the name is changed and if so, update the service offering + if d.HasChange("name") { + log.Printf("[DEBUG] Name changed for %s, starting update", name) + + // Create a new parameter struct + p := cs.ServiceOffering.NewUpdateServiceOfferingParams(d.Id()) + + // Set the new name + p.SetName(d.Get("name").(string)) + + // Update the name + _, err := cs.ServiceOffering.UpdateServiceOffering(p) + if err != nil { + return fmt.Errorf( + "Error updating the name for service offering %s: %s", name, err) + } + + d.SetPartial("name") + } + + // Check if the display text is changed and if so, update seervice offering + if d.HasChange("display_text") { + log.Printf("[DEBUG] Display text changed for %s, starting update", name) + + // Create a new parameter struct + p := cs.ServiceOffering.NewUpdateServiceOfferingParams(d.Id()) + + // Set the new display text + p.SetName(d.Get("display_text").(string)) + + // Update the display text + _, err := cs.ServiceOffering.UpdateServiceOffering(p) + if err != nil { + return fmt.Errorf( + "Error updating the display text for service offering %s: %s", name, err) + } + + d.SetPartial("display_text") + } + + d.Partial(false) + + return resourceCloudStackServiceOfferingRead(d, meta) +} + +func resourceCloudStackServiceOfferingDelete(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + // Create a new parameter struct + p := cs.ServiceOffering.NewDeleteServiceOfferingParams(d.Id()) + _, err := cs.ServiceOffering.DeleteServiceOffering(p) + + if err != nil { + return fmt.Errorf("Error deleting Service Offering: %s", err) + } + + return nil +} From cc3275cf0171729944462b3e5017d73cd72d0f4c Mon Sep 17 00:00:00 2001 From: damans227 Date: Fri, 15 Jul 2022 18:15:18 -0400 Subject: [PATCH 22/39] create service offering data source --- ...data_source_cloudstack_service_offering.go | 138 ++++++++++++++++++ cloudstack/provider.go | 1 + .../resource_cloudstack_service_offering.go | 8 +- 3 files changed, 143 insertions(+), 4 deletions(-) create mode 100644 cloudstack/data_source_cloudstack_service_offering.go diff --git a/cloudstack/data_source_cloudstack_service_offering.go b/cloudstack/data_source_cloudstack_service_offering.go new file mode 100644 index 00000000..4b339032 --- /dev/null +++ b/cloudstack/data_source_cloudstack_service_offering.go @@ -0,0 +1,138 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "encoding/json" + "fmt" + "log" + "regexp" + "strings" + "time" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceCloudstackServiceOffering() *schema.Resource { + return &schema.Resource{ + Read: datasourceCloudStackServiceOfferingRead, + Schema: map[string]*schema.Schema{ + "filter": dataSourceFiltersSchema(), + + //Computed values + "name": { + Type: schema.TypeString, + Computed: true, + }, + "display_text": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func datasourceCloudStackServiceOfferingRead(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + p := cs.ServiceOffering.NewListServiceOfferingsParams() + csServiceOfferings, err := cs.ServiceOffering.ListServiceOfferings(p) + + if err != nil { + return fmt.Errorf("Failed to list service offerings: %s", err) + } + + filters := d.Get("filter") + var serviceOfferings []*cloudstack.ServiceOffering + + for _, s := range csServiceOfferings.ServiceOfferings { + match, err := applyServiceOfferingFilters(s, filters.(*schema.Set)) + if err != nil { + return err + } + if match { + serviceOfferings = append(serviceOfferings, s) + } + } + + if len(serviceOfferings) == 0 { + return fmt.Errorf("No service offering is matching with the specified regex") + } + //return the latest service offering from the list of filtered service according + //to its creation date + serviceOffering, err := latestServiceOffering(serviceOfferings) + if err != nil { + return err + } + log.Printf("[DEBUG] Selected service offerings: %s\n", serviceOffering.Displaytext) + + return serviceOfferingDescriptionAttributes(d, serviceOffering) +} + +func serviceOfferingDescriptionAttributes(d *schema.ResourceData, serviceOffering *cloudstack.ServiceOffering) error { + d.SetId(serviceOffering.Id) + d.Set("name", serviceOffering.Name) + d.Set("display_text", serviceOffering.Displaytext) + + return nil +} + +func latestServiceOffering(serviceOfferings []*cloudstack.ServiceOffering) (*cloudstack.ServiceOffering, error) { + var latest time.Time + var serviceOffering *cloudstack.ServiceOffering + + for _, s := range serviceOfferings { + created, err := time.Parse("2006-01-02T15:04:05-0700", s.Created) + if err != nil { + return nil, fmt.Errorf("Failed to parse creation date of an service offering: %s", err) + } + + if created.After(latest) { + latest = created + serviceOffering = s + } + } + + return serviceOffering, nil +} + +func applyServiceOfferingFilters(serviceOffering *cloudstack.ServiceOffering, filters *schema.Set) (bool, error) { + var serviceOfferingJSON map[string]interface{} + k, _ := json.Marshal(serviceOffering) + err := json.Unmarshal(k, &serviceOfferingJSON) + if err != nil { + return false, err + } + + for _, f := range filters.List() { + m := f.(map[string]interface{}) + r, err := regexp.Compile(m["value"].(string)) + if err != nil { + return false, fmt.Errorf("Invalid regex: %s", err) + } + updatedName := strings.ReplaceAll(m["name"].(string), "_", "") + serviceOfferingField := serviceOfferingJSON[updatedName].(string) + if !r.MatchString(serviceOfferingField) { + return false, nil + } + + } + return true, nil +} diff --git a/cloudstack/provider.go b/cloudstack/provider.go index 553c4248..93fdc896 100644 --- a/cloudstack/provider.go +++ b/cloudstack/provider.go @@ -83,6 +83,7 @@ func Provider() terraform.ResourceProvider { "cloudstack_instance": dataSourceCloudstackInstance(), "cloudstack_network_offering": dataSourceCloudstackNetworkOffering(), "cloudstack_zone": dataSourceCloudStackZone(), + "cloudstack_service_offering": dataSourceCloudstackServiceOffering(), }, ResourcesMap: map[string]*schema.Resource{ diff --git a/cloudstack/resource_cloudstack_service_offering.go b/cloudstack/resource_cloudstack_service_offering.go index 7916ceef..ad887b13 100644 --- a/cloudstack/resource_cloudstack_service_offering.go +++ b/cloudstack/resource_cloudstack_service_offering.go @@ -38,7 +38,7 @@ func resourceCloudStackServiceOffering() *schema.Resource { Type: schema.TypeString, Required: true, }, - "display_name": { + "display_text": { Type: schema.TypeString, Required: true, }, @@ -49,10 +49,10 @@ func resourceCloudStackServiceOffering() *schema.Resource { func resourceCloudStackServiceOfferingCreate(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) name := d.Get("name").(string) - display_name := d.Get("display_name").(string) + display_text := d.Get("display_text").(string) // Create a new parameter struct - p := cs.ServiceOffering.NewCreateServiceOfferingParams(display_name, name) + p := cs.ServiceOffering.NewCreateServiceOfferingParams(display_text, name) log.Printf("[DEBUG] Creating Service Offering %s", name) s, err := cs.ServiceOffering.CreateServiceOffering(p) @@ -85,7 +85,7 @@ func resourceCloudStackServiceOfferingRead(d *schema.ResourceData, meta interfac d.SetId(s.Id) d.Set("name", s.Name) - d.Set("display_name", s.Displaytext) + d.Set("display_text", s.Displaytext) return nil } From a8fe27e0b97c8f410f51b7483391f342cd84dc1a Mon Sep 17 00:00:00 2001 From: damans227 Date: Tue, 19 Jul 2022 14:14:38 -0400 Subject: [PATCH 23/39] add service offering data source --- ...source_cloudstack_service_offering_test.go | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 cloudstack/data_source_cloudstack_service_offering_test.go diff --git a/cloudstack/data_source_cloudstack_service_offering_test.go b/cloudstack/data_source_cloudstack_service_offering_test.go new file mode 100644 index 00000000..fbc3faa6 --- /dev/null +++ b/cloudstack/data_source_cloudstack_service_offering_test.go @@ -0,0 +1,62 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccServiceOfferingDataSource_basic(t *testing.T) { + resourceName := "cloudstack_service_offering.service-offering-resource" + datasourceName := "cloudstack_service_offering.service-offering-data-source" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testServiceOfferingDataSourceConfig_basic, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(datasourceName, "name", resourceName, "name"), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +const testServiceOfferingDataSourceConfig_basic = ` +resource "cloudstack_service_offering" "service-offering-resource"{ + name = "TestServiceUpdate" + display_text = "DisplayService" + } + + data "cloudstack_service_offering" "service-offering-data-source"{ + filter{ + name = "name" + value="TestServiceUpdate" + } + depends_on = [ + cloudstack_service_offering.service-resource + ] + } + ` From 4ad74f16de121745bd1f2e74d5be0879bdba2a1f Mon Sep 17 00:00:00 2001 From: damans227 Date: Sat, 6 Aug 2022 17:36:39 -0400 Subject: [PATCH 24/39] fix typos in comments for network offering resource --- cloudstack/data_source_cloudstack_network_offering.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cloudstack/data_source_cloudstack_network_offering.go b/cloudstack/data_source_cloudstack_network_offering.go index d1354425..b7801a03 100644 --- a/cloudstack/data_source_cloudstack_network_offering.go +++ b/cloudstack/data_source_cloudstack_network_offering.go @@ -83,7 +83,7 @@ func datasourceCloudStackNetworkOfferingRead(d *schema.ResourceData, meta interf if len(networkOfferings) == 0 { return fmt.Errorf("No network offering is matching with the specified regex") } - //return the latest instance from the list of filtered instances according + //return the latest network offering from the list of filtered network offerings according //to its creation date networkOffering, err := latestNetworkOffering(networkOfferings) if err != nil { @@ -111,7 +111,7 @@ func latestNetworkOffering(networkOfferings []*cloudstack.NetworkOffering) (*clo for _, n := range networkOfferings { created, err := time.Parse("2006-01-02T15:04:05-0700", n.Created) if err != nil { - return nil, fmt.Errorf("Failed to parse creation date of an instance: %s", err) + return nil, fmt.Errorf("Failed to parse creation date of a network offering: %s", err) } if created.After(latest) { From fc488e0b688ebb0d4e099b6a374683c8698e6eb2 Mon Sep 17 00:00:00 2001 From: damans227 Date: Thu, 11 Aug 2022 19:17:19 -0400 Subject: [PATCH 25/39] add volume data source --- cloudstack/data_source_cloudstack_volume.go | 143 ++++++++++++++++++++ cloudstack/provider.go | 1 + 2 files changed, 144 insertions(+) create mode 100644 cloudstack/data_source_cloudstack_volume.go diff --git a/cloudstack/data_source_cloudstack_volume.go b/cloudstack/data_source_cloudstack_volume.go new file mode 100644 index 00000000..e8c6bdb0 --- /dev/null +++ b/cloudstack/data_source_cloudstack_volume.go @@ -0,0 +1,143 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "encoding/json" + "fmt" + "log" + "regexp" + "strings" + "time" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceCloudstackVolume() *schema.Resource { + return &schema.Resource{ + Read: datasourceCloudStackVolumeRead, + Schema: map[string]*schema.Schema{ + "filter": dataSourceFiltersSchema(), + + //Computed values + "name": { + Type: schema.TypeString, + Computed: true, + }, + "disk_offering_id": { + Type: schema.TypeString, + Computed: true, + }, + "zone_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func datasourceCloudStackVolumeRead(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + p := cs.Volume.NewListVolumesParams() + csVolumes, err := cs.Volume.ListVolumes(p) + + if err != nil { + return fmt.Errorf("Failed to list volumes: %s", err) + } + + filters := d.Get("filter") + var volumes []*cloudstack.Volume + + for _, v := range csVolumes.Volumes { + match, err := applyVolumeFilters(v, filters.(*schema.Set)) + if err != nil { + return err + } + if match { + volumes = append(volumes, v) + } + } + + if len(volumes) == 0 { + return fmt.Errorf("No volume is matching with the specified regex") + } + //return the latest volume from the list of filtered volumes according + //to its creation date + volume, err := latestVolume(volumes) + if err != nil { + return err + } + log.Printf("[DEBUG] Selected volume: %s\n", volume.Name) + + return volumeDescriptionAttributes(d, volume) +} + +func volumeDescriptionAttributes(d *schema.ResourceData, volume *cloudstack.Volume) error { + d.SetId(volume.Id) + d.Set("name", volume.Name) + d.Set("disk_offering_id", volume.Diskofferingid) + d.Set("zone_id", volume.Zoneid) + + return nil +} + +func latestVolume(volumes []*cloudstack.Volume) (*cloudstack.Volume, error) { + var latest time.Time + var volume *cloudstack.Volume + + for _, v := range volumes { + created, err := time.Parse("2006-01-02T15:04:05-0700", v.Created) + if err != nil { + return nil, fmt.Errorf("Failed to parse creation date of a volume: %s", err) + } + + if created.After(latest) { + latest = created + volume = v + } + } + + return volume, nil +} + +func applyVolumeFilters(volume *cloudstack.Volume, filters *schema.Set) (bool, error) { + var volumeJSON map[string]interface{} + v, _ := json.Marshal(volume) + err := json.Unmarshal(v, &volumeJSON) + if err != nil { + return false, err + } + + for _, f := range filters.List() { + m := f.(map[string]interface{}) + r, err := regexp.Compile(m["value"].(string)) + if err != nil { + return false, fmt.Errorf("Invalid regex: %s", err) + } + updatedName := strings.ReplaceAll(m["name"].(string), "_", "") + volume := volumeJSON[updatedName].(string) + if !r.MatchString(volume) { + return false, nil + } + + } + return true, nil +} diff --git a/cloudstack/provider.go b/cloudstack/provider.go index 93fdc896..928aa82b 100644 --- a/cloudstack/provider.go +++ b/cloudstack/provider.go @@ -84,6 +84,7 @@ func Provider() terraform.ResourceProvider { "cloudstack_network_offering": dataSourceCloudstackNetworkOffering(), "cloudstack_zone": dataSourceCloudStackZone(), "cloudstack_service_offering": dataSourceCloudstackServiceOffering(), + "cloudstack_volume": dataSourceCloudstackVolume(), }, ResourcesMap: map[string]*schema.Resource{ From d4ee48177c2653d27766ca113af54c9984bbeff9 Mon Sep 17 00:00:00 2001 From: damans227 Date: Mon, 15 Aug 2022 19:51:59 -0400 Subject: [PATCH 26/39] add acceptance test for volume data source --- .../data_source_cloudstack_volume_test.go | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 cloudstack/data_source_cloudstack_volume_test.go diff --git a/cloudstack/data_source_cloudstack_volume_test.go b/cloudstack/data_source_cloudstack_volume_test.go new file mode 100644 index 00000000..147a7015 --- /dev/null +++ b/cloudstack/data_source_cloudstack_volume_test.go @@ -0,0 +1,63 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccVolumeDataSource_basic(t *testing.T) { + resourceName := "cloudstack_volume.volume-resource" + datasourceName := "data.cloudstack_volume.volume-data-source" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testVolumeDataSourceConfig_basic, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(datasourceName, "name", resourceName, "name"), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +const testVolumeDataSourceConfig_basic = ` +resource "cloudstack_volume" "volume-resource"{ + name = "TestVolume" + disk_offering_id = "0038adec-5e3e-47df-b4b4-77b5dc8e3338" + zone_id = "9a7002b2-09a2-44dc-a332-f2e4e7f01539" + } + + data "cloudstack_volume" "volume-data-source"{ + filter{ + name = "name" + value="TestVolume" + } + depends_on = [ + cloudstack_volume.volume-resource + ] + } + ` From eae747c0d2c0bab27056aeb90d3704156f81de1e Mon Sep 17 00:00:00 2001 From: damans227 Date: Tue, 16 Aug 2022 20:48:31 -0400 Subject: [PATCH 27/39] create account resource --- cloudstack/provider.go | 1 + cloudstack/resource_cloudstack_account.go | 100 ++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 cloudstack/resource_cloudstack_account.go diff --git a/cloudstack/provider.go b/cloudstack/provider.go index 928aa82b..ebe3a2f9 100644 --- a/cloudstack/provider.go +++ b/cloudstack/provider.go @@ -118,6 +118,7 @@ func Provider() terraform.ResourceProvider { "cloudstack_volume": resourceCloudStackVolume(), "cloudstack_zone": resourceCloudStackZone(), "cloudstack_service_offering": resourceCloudStackServiceOffering(), + "cloudstack_account": resourceCloudStackAccount(), }, ConfigureFunc: providerConfigure, diff --git a/cloudstack/resource_cloudstack_account.go b/cloudstack/resource_cloudstack_account.go new file mode 100644 index 00000000..0616db6f --- /dev/null +++ b/cloudstack/resource_cloudstack_account.go @@ -0,0 +1,100 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "log" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceCloudStackAccount() *schema.Resource { + return &schema.Resource{ + Read: resourceCloudStackAccountRead, + Update: resourceCloudStackAccountUpdate, + Create: resourceCloudStackAccountCreate, + Delete: resourceCloudStackAccountDelete, + Schema: map[string]*schema.Schema{ + "email": { + Type: schema.TypeString, + Required: true, + }, + "first_name": { + Type: schema.TypeString, + Required: true, + }, + "last_name": { + Type: schema.TypeString, + Required: true, + }, + "password": { + Type: schema.TypeString, + Required: true, + }, + "username": { + Type: schema.TypeString, + Required: true, + }, + "account_type": { + Type: schema.TypeInt, + Required: true, + }, + "role_id": { + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +func resourceCloudStackAccountCreate(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + email := d.Get("email").(string) + first_name := d.Get("first_name").(string) + last_name := d.Get("last_name").(string) + username := d.Get("username").(string) + password := d.Get("password").(string) + role_id := d.Get("role_id").(string) + account_type := d.Get("account_type").(int) + + // Create a new parameter struct + p := cs.Account.NewCreateAccountParams(email, first_name, last_name, password, username) + p.SetAccounttype(int(account_type)) + p.SetRoleid(role_id) + + log.Printf("[DEBUG] Creating Account %s", username) + account, err := cs.Account.CreateAccount(p) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Account %s successfully created", username) + d.SetId(account.Id) + + return resourceCloudStackAccountRead(d, meta) +} + +func resourceCloudStackAccountRead(d *schema.ResourceData, meta interface{}) error { return nil } + +func resourceCloudStackAccountUpdate(d *schema.ResourceData, meta interface{}) error { return nil } + +func resourceCloudStackAccountDelete(d *schema.ResourceData, meta interface{}) error { return nil } From c78b1fc05ee1e962bec94e7682d2ee1af64ae6ba Mon Sep 17 00:00:00 2001 From: damans227 Date: Thu, 18 Aug 2022 20:53:49 -0400 Subject: [PATCH 28/39] implement delete method in account resource --- cloudstack/resource_cloudstack_account.go | 33 +++++++++++++++++++---- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/cloudstack/resource_cloudstack_account.go b/cloudstack/resource_cloudstack_account.go index 0616db6f..a0d950f9 100644 --- a/cloudstack/resource_cloudstack_account.go +++ b/cloudstack/resource_cloudstack_account.go @@ -20,6 +20,7 @@ package cloudstack import ( + "fmt" "log" "github.com/apache/cloudstack-go/v2/cloudstack" @@ -61,6 +62,10 @@ func resourceCloudStackAccount() *schema.Resource { Type: schema.TypeString, Required: true, }, + "account": { + Type: schema.TypeString, + Optional: true, + }, }, } } @@ -74,21 +79,27 @@ func resourceCloudStackAccountCreate(d *schema.ResourceData, meta interface{}) e password := d.Get("password").(string) role_id := d.Get("role_id").(string) account_type := d.Get("account_type").(int) + account := d.Get("account").(string) // Create a new parameter struct p := cs.Account.NewCreateAccountParams(email, first_name, last_name, password, username) p.SetAccounttype(int(account_type)) p.SetRoleid(role_id) + if account != "" { + p.SetAccount(account) + } else { + p.SetAccount(username) + } - log.Printf("[DEBUG] Creating Account %s", username) - account, err := cs.Account.CreateAccount(p) + log.Printf("[DEBUG] Creating Account %s", account) + a, err := cs.Account.CreateAccount(p) if err != nil { return err } - log.Printf("[DEBUG] Account %s successfully created", username) - d.SetId(account.Id) + log.Printf("[DEBUG] Account %s successfully created", account) + d.SetId(a.Id) return resourceCloudStackAccountRead(d, meta) } @@ -97,4 +108,16 @@ func resourceCloudStackAccountRead(d *schema.ResourceData, meta interface{}) err func resourceCloudStackAccountUpdate(d *schema.ResourceData, meta interface{}) error { return nil } -func resourceCloudStackAccountDelete(d *schema.ResourceData, meta interface{}) error { return nil } +func resourceCloudStackAccountDelete(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + // Create a new parameter struct + p := cs.Account.NewDeleteAccountParams(d.Id()) + _, err := cs.Account.DeleteAccount(p) + + if err != nil { + return fmt.Errorf("Error deleting Account: %s", err) + } + + return nil +} From bce3a32c9daa1f3751436447057bb6b46582f241 Mon Sep 17 00:00:00 2001 From: damans227 Date: Thu, 18 Aug 2022 20:56:50 -0400 Subject: [PATCH 29/39] fix a typo in network offering --- cloudstack/resource_cloudstack_network_offering.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudstack/resource_cloudstack_network_offering.go b/cloudstack/resource_cloudstack_network_offering.go index dd17d96b..e11ece26 100644 --- a/cloudstack/resource_cloudstack_network_offering.go +++ b/cloudstack/resource_cloudstack_network_offering.go @@ -181,7 +181,7 @@ func resourceCloudStackNetworkOfferingDelete(d *schema.ResourceData, meta interf _, err := cs.NetworkOffering.DeleteNetworkOffering(p) if err != nil { - return fmt.Errorf("Error deleting Kubernetes Cluster: %s", err) + return fmt.Errorf("Error deleting Network Offering: %s", err) } return nil From 5015e80a8c10d2eb8159453a3f544ec37e9e3664 Mon Sep 17 00:00:00 2001 From: damans227 Date: Sun, 21 Aug 2022 15:13:14 -0400 Subject: [PATCH 30/39] create user resource --- cloudstack/provider.go | 1 + cloudstack/resource_cloudstack_user.go | 110 +++++++++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 cloudstack/resource_cloudstack_user.go diff --git a/cloudstack/provider.go b/cloudstack/provider.go index ebe3a2f9..81fd8e4b 100644 --- a/cloudstack/provider.go +++ b/cloudstack/provider.go @@ -119,6 +119,7 @@ func Provider() terraform.ResourceProvider { "cloudstack_zone": resourceCloudStackZone(), "cloudstack_service_offering": resourceCloudStackServiceOffering(), "cloudstack_account": resourceCloudStackAccount(), + "cloudstack_user": resourceCloudStackUser(), }, ConfigureFunc: providerConfigure, diff --git a/cloudstack/resource_cloudstack_user.go b/cloudstack/resource_cloudstack_user.go new file mode 100644 index 00000000..05336264 --- /dev/null +++ b/cloudstack/resource_cloudstack_user.go @@ -0,0 +1,110 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "fmt" + "log" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceCloudStackUser() *schema.Resource { + return &schema.Resource{ + Create: resourceCloudStackUserCreate, + Read: resourceCloudStackUserRead, + Update: resourceCloudStackUserUpdate, + Delete: resourceCloudStackUserDelete, + Schema: map[string]*schema.Schema{ + "account": { + Type: schema.TypeString, + Optional: true, + }, + "email": { + Type: schema.TypeString, + Required: true, + }, + "first_name": { + Type: schema.TypeString, + Required: true, + }, + "last_name": { + Type: schema.TypeString, + Required: true, + }, + "password": { + Type: schema.TypeString, + Required: true, + }, + "username": { + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +func resourceCloudStackUserCreate(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + account := d.Get("account").(string) + email := d.Get("email").(string) + first_name := d.Get("first_name").(string) + last_name := d.Get("last_name").(string) + password := d.Get("password").(string) + username := d.Get("username").(string) + + // Create a new parameter struct + p := cs.User.NewCreateUserParams(account, email, first_name, last_name, password, username) + + log.Printf("[DEBUG] Creating User %s", username) + u, err := cs.User.CreateUser(p) + + if err != nil { + return err + } + + log.Printf("[DEBUG] User %s successfully created", username) + d.SetId(u.Id) + + return resourceCloudStackUserRead(d, meta) +} + +func resourceCloudStackUserUpdate(d *schema.ResourceData, meta interface{}) error { + return resourceCloudStackInstanceRead(d, meta) +} + +func resourceCloudStackUserDelete(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + // Create a new parameter struct + p := cs.User.NewDeleteUserParams(d.Id()) + _, err := cs.User.DeleteUser(p) + + if err != nil { + return fmt.Errorf("Error deleting User: %s", err) + } + + return nil +} + +func resourceCloudStackUserRead(d *schema.ResourceData, meta interface{}) error { + return nil +} From b0f537100f9171cec78ddf5683206e0c3cda3e83 Mon Sep 17 00:00:00 2001 From: damans227 Date: Sun, 21 Aug 2022 21:03:33 -0400 Subject: [PATCH 31/39] create domain resource --- cloudstack/provider.go | 1 + cloudstack/resource_cloudstack_domain.go | 108 +++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 cloudstack/resource_cloudstack_domain.go diff --git a/cloudstack/provider.go b/cloudstack/provider.go index 81fd8e4b..c3dfd0aa 100644 --- a/cloudstack/provider.go +++ b/cloudstack/provider.go @@ -120,6 +120,7 @@ func Provider() terraform.ResourceProvider { "cloudstack_service_offering": resourceCloudStackServiceOffering(), "cloudstack_account": resourceCloudStackAccount(), "cloudstack_user": resourceCloudStackUser(), + "cloudstack_domain": resourceCloudStackDomain(), }, ConfigureFunc: providerConfigure, diff --git a/cloudstack/resource_cloudstack_domain.go b/cloudstack/resource_cloudstack_domain.go new file mode 100644 index 00000000..4123a6e6 --- /dev/null +++ b/cloudstack/resource_cloudstack_domain.go @@ -0,0 +1,108 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "fmt" + "log" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceCloudStackDomain() *schema.Resource { + return &schema.Resource{ + Read: resourceCloudStackDomainRead, + Update: resourceCloudStackDomainUpdate, + Create: resourceCloudStackDomainCreate, + Delete: resourceCloudStackDomainDelete, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "domain_id": { + Type: schema.TypeString, + Optional: true, + }, + "network_domain": { + Type: schema.TypeString, + Optional: true, + }, + "parent_domain_id": { + Type: schema.TypeString, + Optional: true, + }, + }, + } +} + +func resourceCloudStackDomainCreate(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + name := d.Get("name").(string) + domain_id := d.Get("domain_id").(string) + network_domain := d.Get("network_domain").(string) + parent_domain_id := d.Get("parent_domain_id").(string) + + // Create a new parameter struct + p := cs.Domain.NewCreateDomainParams(name) + + if domain_id != "" { + p.SetDomainid(domain_id) + } + + if network_domain != "" { + p.SetNetworkdomain(network_domain) + } + + if parent_domain_id != "" { + p.SetParentdomainid(parent_domain_id) + } + + log.Printf("[DEBUG] Creating Domain %s", name) + domain, err := cs.Domain.CreateDomain(p) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Domain %s successfully created", name) + d.SetId(domain.Id) + + return resourceCloudStackDomainRead(d, meta) +} + +func resourceCloudStackDomainRead(d *schema.ResourceData, meta interface{}) error { return nil } + +func resourceCloudStackDomainUpdate(d *schema.ResourceData, meta interface{}) error { return nil } + +func resourceCloudStackDomainDelete(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + // Create a new parameter struct + p := cs.Domain.NewDeleteDomainParams(d.Id()) + _, err := cs.Domain.DeleteDomain(p) + + if err != nil { + return fmt.Errorf("Error deleting Domain: %s", err) + } + + return nil +} From 0ad1d9631764fe1a39de3ee9ae6c951d326c27a1 Mon Sep 17 00:00:00 2001 From: damans227 Date: Sat, 27 Aug 2022 15:20:33 -0400 Subject: [PATCH 32/39] add vpc data-source --- cloudstack/data_source_cloudstack_vpc.go | 173 +++++++++++++++++++++++ cloudstack/provider.go | 1 + 2 files changed, 174 insertions(+) create mode 100644 cloudstack/data_source_cloudstack_vpc.go diff --git a/cloudstack/data_source_cloudstack_vpc.go b/cloudstack/data_source_cloudstack_vpc.go new file mode 100644 index 00000000..a473d99c --- /dev/null +++ b/cloudstack/data_source_cloudstack_vpc.go @@ -0,0 +1,173 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "encoding/json" + "fmt" + "log" + "regexp" + "strings" + "time" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceCloudstackVPC() *schema.Resource { + return &schema.Resource{ + Read: datasourceCloudStackVPCRead, + Schema: map[string]*schema.Schema{ + "filter": dataSourceFiltersSchema(), + + //Computed values + "name": { + Type: schema.TypeString, + Computed: true, + }, + + "display_text": { + Type: schema.TypeString, + Computed: true, + }, + + "cidr": { + Type: schema.TypeString, + Computed: true, + }, + + "vpc_offering_name": { + Type: schema.TypeString, + Computed: true, + }, + + "network_domain": { + Type: schema.TypeString, + Computed: true, + }, + + "project": { + Type: schema.TypeString, + Computed: true, + }, + + "zone_name": { + Type: schema.TypeString, + Computed: true, + }, + + "tags": tagsSchema(), + }, + } +} + +func datasourceCloudStackVPCRead(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + p := cs.VPC.NewListVPCsParams() + csVPCs, err := cs.VPC.ListVPCs(p) + + if err != nil { + return fmt.Errorf("Failed to list VPCs: %s", err) + } + + filters := d.Get("filter") + var vpcs []*cloudstack.VPC + + for _, v := range csVPCs.VPCs { + match, err := applyVPCFilters(v, filters.(*schema.Set)) + if err != nil { + return err + } + if match { + vpcs = append(vpcs, v) + } + } + + if len(vpcs) == 0 { + return fmt.Errorf("No VPC is matching with the specified regex") + } + //return the latest VPC from the list of filtered VPCs according + //to its creation date + vpc, err := latestVPC(vpcs) + if err != nil { + return err + } + log.Printf("[DEBUG] Selected VPCs: %s\n", vpc.Displaytext) + + return vpcDescriptionAttributes(d, vpc) +} + +func vpcDescriptionAttributes(d *schema.ResourceData, vpc *cloudstack.VPC) error { + d.SetId(vpc.Id) + d.Set("name", vpc.Name) + d.Set("display_text", vpc.Displaytext) + d.Set("cidr", vpc.Cidr) + d.Set("vpc_offering_name", vpc.Vpcofferingname) + d.Set("network_domain", vpc.Networkdomain) + d.Set("project", vpc.Project) + d.Set("zone_name", vpc.Zonename) + d.Set("tags", vpc.Tags) + + return nil +} + +func latestVPC(vpcs []*cloudstack.VPC) (*cloudstack.VPC, error) { + var latest time.Time + var vpc *cloudstack.VPC + + for _, v := range vpcs { + created, err := time.Parse("2006-01-02T15:04:05-0700", v.Created) + if err != nil { + return nil, fmt.Errorf("Failed to parse creation date of a VPC: %s", err) + } + + if created.After(latest) { + latest = created + vpc = v + } + } + + return vpc, nil +} + +func applyVPCFilters(vpc *cloudstack.VPC, filters *schema.Set) (bool, error) { + var vpcJSON map[string]interface{} + k, _ := json.Marshal(vpc) + err := json.Unmarshal(k, &vpcJSON) + if err != nil { + return false, err + } + + for _, f := range filters.List() { + m := f.(map[string]interface{}) + log.Print(m) + r, err := regexp.Compile(m["value"].(string)) + if err != nil { + return false, fmt.Errorf("Invalid regex: %s", err) + } + updatedName := strings.ReplaceAll(m["name"].(string), "_", "") + log.Print(updatedName) + vpcField := vpcJSON[updatedName].(string) + if !r.MatchString(vpcField) { + return false, nil + } + } + return true, nil +} diff --git a/cloudstack/provider.go b/cloudstack/provider.go index c3dfd0aa..18d8c760 100644 --- a/cloudstack/provider.go +++ b/cloudstack/provider.go @@ -85,6 +85,7 @@ func Provider() terraform.ResourceProvider { "cloudstack_zone": dataSourceCloudStackZone(), "cloudstack_service_offering": dataSourceCloudstackServiceOffering(), "cloudstack_volume": dataSourceCloudstackVolume(), + "cloudstack_vpc": dataSourceCloudstackVPC(), }, ResourcesMap: map[string]*schema.Resource{ From b406da4e450df3d30b625bf9f84e44706b638c03 Mon Sep 17 00:00:00 2001 From: damans227 Date: Sat, 27 Aug 2022 16:30:17 -0400 Subject: [PATCH 33/39] add ip address data-source --- .../data_source_cloudstack_ipaddress.go | 175 ++++++++++++++++++ cloudstack/provider.go | 1 + 2 files changed, 176 insertions(+) create mode 100644 cloudstack/data_source_cloudstack_ipaddress.go diff --git a/cloudstack/data_source_cloudstack_ipaddress.go b/cloudstack/data_source_cloudstack_ipaddress.go new file mode 100644 index 00000000..ad7b1085 --- /dev/null +++ b/cloudstack/data_source_cloudstack_ipaddress.go @@ -0,0 +1,175 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "encoding/json" + "fmt" + "log" + "regexp" + "strings" + "time" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceCloudstackIPAddress() *schema.Resource { + return &schema.Resource{ + Read: datasourceCloudStackIPAddressRead, + Schema: map[string]*schema.Schema{ + "filter": dataSourceFiltersSchema(), + + //Computed values + "is_portable": { + Type: schema.TypeBool, + Computed: true, + }, + + "network_id": { + Type: schema.TypeString, + Computed: true, + }, + + "vpc_id": { + Type: schema.TypeString, + Computed: true, + }, + + "zone_name": { + Type: schema.TypeString, + Computed: true, + }, + + "project": { + Type: schema.TypeString, + Computed: true, + }, + + "ip_address": { + Type: schema.TypeString, + Computed: true, + }, + + "is_source_nat": { + Type: schema.TypeBool, + Computed: true, + }, + + "tags": tagsSchema(), + }, + } +} + +func datasourceCloudStackIPAddressRead(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + p := cs.Address.NewListPublicIpAddressesParams() + csPublicIPAddresses, err := cs.Address.ListPublicIpAddresses(p) + + if err != nil { + return fmt.Errorf("Failed to list ip addresses: %s", err) + } + + filters := d.Get("filter") + var publicIpAddresses []*cloudstack.PublicIpAddress + + for _, ip := range csPublicIPAddresses.PublicIpAddresses { + match, err := applyIPAddressFilters(ip, filters.(*schema.Set)) + + if err != nil { + return err + } + if match { + publicIpAddresses = append(publicIpAddresses, ip) + } + } + + if len(publicIpAddresses) == 0 { + return fmt.Errorf("No ip address is matching with the specified regex") + } + //return the latest ip address from the list of filtered ip addresses according + //to its creation date + publicIpAddress, err := latestIPAddress(publicIpAddresses) + if err != nil { + return err + } + log.Printf("[DEBUG] Selected ip addresses: %s\n", publicIpAddress.Ipaddress) + + return ipAddressDescriptionAttributes(d, publicIpAddress) +} + +func ipAddressDescriptionAttributes(d *schema.ResourceData, publicIpAddress *cloudstack.PublicIpAddress) error { + d.SetId(publicIpAddress.Id) + d.Set("is_portable", publicIpAddress.Isportable) + d.Set("network_id", publicIpAddress.Networkid) + d.Set("vpc_id", publicIpAddress.Vpcid) + d.Set("zone_name", publicIpAddress.Zonename) + d.Set("project", publicIpAddress.Project) + d.Set("ip_address", publicIpAddress.Ipaddress) + d.Set("is_source_nat", publicIpAddress.Issourcenat) + d.Set("tags", publicIpAddress.Tags) + + return nil +} + +func latestIPAddress(publicIpAddresses []*cloudstack.PublicIpAddress) (*cloudstack.PublicIpAddress, error) { + var latest time.Time + var publicIpAddress *cloudstack.PublicIpAddress + + for _, ip := range publicIpAddresses { + created, err := time.Parse("2006-01-02T15:04:05-0700", ip.Allocated) + + if err != nil { + return nil, fmt.Errorf("Failed to parse allocation date of the ip address: %s", err) + } + + if created.After(latest) { + latest = created + publicIpAddress = ip + } + } + + return publicIpAddress, nil +} + +func applyIPAddressFilters(publicIpAddress *cloudstack.PublicIpAddress, filters *schema.Set) (bool, error) { + var publicIPAdressJSON map[string]interface{} + k, _ := json.Marshal(publicIpAddress) + err := json.Unmarshal(k, &publicIPAdressJSON) + + if err != nil { + return false, err + } + + for _, f := range filters.List() { + m := f.(map[string]interface{}) + r, err := regexp.Compile(m["value"].(string)) + if err != nil { + return false, fmt.Errorf("Invalid regex: %s", err) + } + updatedName := strings.ReplaceAll(m["name"].(string), "_", "") + publicIPAdressField := fmt.Sprintf("%v", publicIPAdressJSON[updatedName]) + if !r.MatchString(publicIPAdressField) { + return false, nil + } + } + + return true, nil +} diff --git a/cloudstack/provider.go b/cloudstack/provider.go index 18d8c760..419b6afd 100644 --- a/cloudstack/provider.go +++ b/cloudstack/provider.go @@ -86,6 +86,7 @@ func Provider() terraform.ResourceProvider { "cloudstack_service_offering": dataSourceCloudstackServiceOffering(), "cloudstack_volume": dataSourceCloudstackVolume(), "cloudstack_vpc": dataSourceCloudstackVPC(), + "cloudstack_ipaddress": dataSourceCloudstackIPAddress(), }, ResourcesMap: map[string]*schema.Resource{ From 1c198673604ff4a71b7c756813814290e9b23353 Mon Sep 17 00:00:00 2001 From: damans227 Date: Sat, 27 Aug 2022 17:32:44 -0400 Subject: [PATCH 34/39] add user data-source --- cloudstack/data_source_cloudstack_user.go | 154 ++++++++++++++++++++++ cloudstack/provider.go | 1 + 2 files changed, 155 insertions(+) create mode 100644 cloudstack/data_source_cloudstack_user.go diff --git a/cloudstack/data_source_cloudstack_user.go b/cloudstack/data_source_cloudstack_user.go new file mode 100644 index 00000000..a40862aa --- /dev/null +++ b/cloudstack/data_source_cloudstack_user.go @@ -0,0 +1,154 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "encoding/json" + "fmt" + "log" + "regexp" + "strings" + "time" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceCloudstackUser() *schema.Resource { + return &schema.Resource{ + Read: datasourceCloudStackUserRead, + Schema: map[string]*schema.Schema{ + "filter": dataSourceFiltersSchema(), + + //Computed values + "account": { + Type: schema.TypeString, + Computed: true, + }, + "email": { + Type: schema.TypeString, + Computed: true, + }, + "first_name": { + Type: schema.TypeString, + Computed: true, + }, + "last_name": { + Type: schema.TypeString, + Computed: true, + }, + "username": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func datasourceCloudStackUserRead(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + p := cs.User.NewListUsersParams() + csUsers, err := cs.User.ListUsers(p) + + if err != nil { + return fmt.Errorf("Failed to list users: %s", err) + } + + filters := d.Get("filter") + var users []*cloudstack.User + + for _, u := range csUsers.Users { + match, err := applyUserFilters(u, filters.(*schema.Set)) + if err != nil { + return err + } + if match { + users = append(users, u) + } + } + + if len(users) == 0 { + return fmt.Errorf("No user is matching with the specified regex") + } + //return the latest user from the list of filtered userss according + //to its creation date + user, err := latestUser(users) + if err != nil { + return err + } + log.Printf("[DEBUG] Selected users: %s\n", user.Username) + + return userDescriptionAttributes(d, user) +} + +func userDescriptionAttributes(d *schema.ResourceData, user *cloudstack.User) error { + d.SetId(user.Id) + d.Set("account", user.Account) + d.Set("email", user.Email) + d.Set("first_name", user.Firstname) + d.Set("last_name", user.Lastname) + d.Set("username", user.Username) + + return nil +} + +func latestUser(users []*cloudstack.User) (*cloudstack.User, error) { + var latest time.Time + var user *cloudstack.User + + for _, u := range users { + created, err := time.Parse("2006-01-02T15:04:05-0700", u.Created) + if err != nil { + return nil, fmt.Errorf("Failed to parse creation date of a user: %s", err) + } + + if created.After(latest) { + latest = created + user = u + } + } + + return user, nil +} + +func applyUserFilters(user *cloudstack.User, filters *schema.Set) (bool, error) { + var userJSON map[string]interface{} + k, _ := json.Marshal(user) + err := json.Unmarshal(k, &userJSON) + if err != nil { + return false, err + } + + for _, f := range filters.List() { + m := f.(map[string]interface{}) + log.Print(m) + r, err := regexp.Compile(m["value"].(string)) + if err != nil { + return false, fmt.Errorf("Invalid regex: %s", err) + } + updatedName := strings.ReplaceAll(m["name"].(string), "_", "") + log.Print(updatedName) + userField := userJSON[updatedName].(string) + if !r.MatchString(userField) { + return false, nil + } + } + return true, nil +} diff --git a/cloudstack/provider.go b/cloudstack/provider.go index 419b6afd..da82eccf 100644 --- a/cloudstack/provider.go +++ b/cloudstack/provider.go @@ -87,6 +87,7 @@ func Provider() terraform.ResourceProvider { "cloudstack_volume": dataSourceCloudstackVolume(), "cloudstack_vpc": dataSourceCloudstackVPC(), "cloudstack_ipaddress": dataSourceCloudstackIPAddress(), + "cloudstack_user": dataSourceCloudstackUser(), }, ResourcesMap: map[string]*schema.Resource{ From 4c4698374421e00b7637a9aa2e2727feb437112e Mon Sep 17 00:00:00 2001 From: damans227 Date: Sat, 27 Aug 2022 18:09:35 -0400 Subject: [PATCH 35/39] add vpn connection data-source --- .../data_source_cloudstack_vpn_connection.go | 140 ++++++++++++++++++ cloudstack/provider.go | 1 + 2 files changed, 141 insertions(+) create mode 100644 cloudstack/data_source_cloudstack_vpn_connection.go diff --git a/cloudstack/data_source_cloudstack_vpn_connection.go b/cloudstack/data_source_cloudstack_vpn_connection.go new file mode 100644 index 00000000..c0015bf6 --- /dev/null +++ b/cloudstack/data_source_cloudstack_vpn_connection.go @@ -0,0 +1,140 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "encoding/json" + "fmt" + "log" + "regexp" + "strings" + "time" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceCloudstackVPNConnection() *schema.Resource { + return &schema.Resource{ + Read: datasourceCloudStackVPNConnectionRead, + Schema: map[string]*schema.Schema{ + "filter": dataSourceFiltersSchema(), + + //Computed values + "s2s_customer_gateway_id": { + Type: schema.TypeString, + Computed: true, + }, + + "s2s_vpn_gateway_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func datasourceCloudStackVPNConnectionRead(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + p := cs.VPN.NewListVpnConnectionsParams() + csVPNConnections, err := cs.VPN.ListVpnConnections(p) + + if err != nil { + return fmt.Errorf("Failed to list VPNs: %s", err) + } + + filters := d.Get("filter") + var vpnConnections []*cloudstack.VpnConnection + + for _, v := range csVPNConnections.VpnConnections { + match, err := applyVPNConnectionFilters(v, filters.(*schema.Set)) + if err != nil { + return err + } + if match { + vpnConnections = append(vpnConnections, v) + } + } + + if len(vpnConnections) == 0 { + return fmt.Errorf("No VPN Connection is matching with the specified regex") + } + //return the latest VPN Connection from the list of filtered VPN Connections according + //to its creation date + vpnConnection, err := latestVPNConnection(vpnConnections) + if err != nil { + return err + } + log.Printf("[DEBUG] Selected VPN Connections: %s\n", vpnConnection.Id) + + return vpnConnectionDescriptionAttributes(d, vpnConnection) +} + +func vpnConnectionDescriptionAttributes(d *schema.ResourceData, vpnConnection *cloudstack.VpnConnection) error { + d.SetId(vpnConnection.Id) + d.Set("s2s_customer_gateway_id", vpnConnection.S2scustomergatewayid) + d.Set("s2s_vpn_gateway_id", vpnConnection.S2svpngatewayid) + + return nil +} + +func latestVPNConnection(vpnConnections []*cloudstack.VpnConnection) (*cloudstack.VpnConnection, error) { + var latest time.Time + var vpnConnection *cloudstack.VpnConnection + + for _, v := range vpnConnections { + created, err := time.Parse("2006-01-02T15:04:05-0700", v.Created) + if err != nil { + return nil, fmt.Errorf("Failed to parse creation date of a VPN Connection: %s", err) + } + + if created.After(latest) { + latest = created + vpnConnection = v + } + } + + return vpnConnection, nil +} + +func applyVPNConnectionFilters(vpnConnection *cloudstack.VpnConnection, filters *schema.Set) (bool, error) { + var vpnConnectionJSON map[string]interface{} + k, _ := json.Marshal(vpnConnection) + err := json.Unmarshal(k, &vpnConnectionJSON) + if err != nil { + return false, err + } + + for _, f := range filters.List() { + m := f.(map[string]interface{}) + log.Print(m) + r, err := regexp.Compile(m["value"].(string)) + if err != nil { + return false, fmt.Errorf("Invalid regex: %s", err) + } + updatedName := strings.ReplaceAll(m["name"].(string), "_", "") + log.Print(updatedName) + vpnConnectionField := vpnConnectionJSON[updatedName].(string) + if !r.MatchString(vpnConnectionField) { + return false, nil + } + } + return true, nil +} diff --git a/cloudstack/provider.go b/cloudstack/provider.go index da82eccf..0cf6ab9e 100644 --- a/cloudstack/provider.go +++ b/cloudstack/provider.go @@ -88,6 +88,7 @@ func Provider() terraform.ResourceProvider { "cloudstack_vpc": dataSourceCloudstackVPC(), "cloudstack_ipaddress": dataSourceCloudstackIPAddress(), "cloudstack_user": dataSourceCloudstackUser(), + "cloudstack_vpn_connection": dataSourceCloudstackVPNConnection(), }, ResourcesMap: map[string]*schema.Resource{ From d8abde28a3d15fd9ea642378d05f305ad97dfe9d Mon Sep 17 00:00:00 2001 From: damans227 Date: Sun, 28 Aug 2022 12:08:48 -0400 Subject: [PATCH 36/39] add acceptance test for vpc data-source --- cloudstack/data_source_cloudstack_vpc_test.go | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 cloudstack/data_source_cloudstack_vpc_test.go diff --git a/cloudstack/data_source_cloudstack_vpc_test.go b/cloudstack/data_source_cloudstack_vpc_test.go new file mode 100644 index 00000000..3920f023 --- /dev/null +++ b/cloudstack/data_source_cloudstack_vpc_test.go @@ -0,0 +1,72 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccVPCDataSource_basic(t *testing.T) { + resourceName := "cloudstack_vpc.vpc-resource" + datasourceName := "data.cloudstack_vpc.vpc-data-source" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testVPCDataSourceConfig_basic, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(datasourceName, "name", resourceName, "name"), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +const testVPCDataSourceConfig_basic = ` +resource "cloudstack_vpc" "vpc-resource" { +name = "test-vpc" +cidr = "10.0.0.0/16" +vpc_offering = "Default VPC Offering" +zone = "DC" +} + +data "cloudstack_vpc" "vpc-data-source"{ + filter{ + name = "name" + value= "test-vpc" + } + filter{ + name = "cidr" + value= "10.0.0.0/16" + } + depends_on = [ + cloudstack_vpc.vpc-resource + ] +} + +output "vpc-output" { +value = "${data.cloudstack_vpc.vpc-data-source}" +} + ` From 4c1e5319d7d79d6e5f131fb984d256e86f8be513 Mon Sep 17 00:00:00 2001 From: damans227 Date: Sun, 28 Aug 2022 12:26:46 -0400 Subject: [PATCH 37/39] add acceptance test for ipaddress data-source --- .../data_source_cloudstack_ipaddress_test.go | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 cloudstack/data_source_cloudstack_ipaddress_test.go diff --git a/cloudstack/data_source_cloudstack_ipaddress_test.go b/cloudstack/data_source_cloudstack_ipaddress_test.go new file mode 100644 index 00000000..ad98201b --- /dev/null +++ b/cloudstack/data_source_cloudstack_ipaddress_test.go @@ -0,0 +1,65 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccIPAddressDataSource_basic(t *testing.T) { + resourceName := "cloudstack_ipaddress.ipaddress-resource" + datasourceName := "data.cloudstack_ipaddress.ipaddress-data-source" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testIPAddressDataSourceConfig_basic, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(datasourceName, "zone_name", resourceName, "zone"), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +const testIPAddressDataSourceConfig_basic = ` +resource "cloudstack_ipaddress" "ipaddress-resource" { + zone = "DC" + } + + data "cloudstack_ipaddress" "ipaddress-data-source"{ + filter{ + name = "zone_name" + value= "DC" + } + depends_on = [ + cloudstack_ipaddress.ipaddress-resource + ] + } + + output "ipaddress-output" { + value = "${data.cloudstack_ipaddress.ipaddress-data-source}" + } + ` From 447e29f06e67884d82d899365cd2cdeb91a3ea3a Mon Sep 17 00:00:00 2001 From: damans227 Date: Sun, 28 Aug 2022 12:39:26 -0400 Subject: [PATCH 38/39] add acceptance test for user data-source --- .../data_source_cloudstack_user_test.go | 70 +++++++++++++++++++ cloudstack/resource_cloudstack_user.go | 2 +- 2 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 cloudstack/data_source_cloudstack_user_test.go diff --git a/cloudstack/data_source_cloudstack_user_test.go b/cloudstack/data_source_cloudstack_user_test.go new file mode 100644 index 00000000..aa9c14d0 --- /dev/null +++ b/cloudstack/data_source_cloudstack_user_test.go @@ -0,0 +1,70 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccUserDataSource_basic(t *testing.T) { + resourceName := "cloudstack_user.user-resource" + datasourceName := "data.cloudstack_user.user-data-source" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testUserDataSourceConfig_basic, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(datasourceName, "first_name", resourceName, "first_name"), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +const testUserDataSourceConfig_basic = ` +resource "cloudstack_user" "user-resource" { + account = "admin" + email = "jon.doe@gmail.com" + first_name = "jon" + last_name = "doe" + password = "password" + username = "jon123" +} + +data "cloudstack_user" "user-data-source"{ + filter{ + name = "first_name" + value= "jon" + } + depends_on = [ + cloudstack_user.user-resource + ] + } + +output "user-output" { + value = "${data.cloudstack_user.user-data-source}" +} + ` diff --git a/cloudstack/resource_cloudstack_user.go b/cloudstack/resource_cloudstack_user.go index 05336264..aabb7b20 100644 --- a/cloudstack/resource_cloudstack_user.go +++ b/cloudstack/resource_cloudstack_user.go @@ -88,7 +88,7 @@ func resourceCloudStackUserCreate(d *schema.ResourceData, meta interface{}) erro } func resourceCloudStackUserUpdate(d *schema.ResourceData, meta interface{}) error { - return resourceCloudStackInstanceRead(d, meta) + return resourceCloudStackUserRead(d, meta) } func resourceCloudStackUserDelete(d *schema.ResourceData, meta interface{}) error { From 04aae0da55acf3bc0434e44fbc8d6c9a556db890 Mon Sep 17 00:00:00 2001 From: damans227 Date: Sun, 28 Aug 2022 12:44:49 -0400 Subject: [PATCH 39/39] fix a typo in applyNetworkOfferingFilters method of network offering data-source --- cloudstack/data_source_cloudstack_network_offering.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cloudstack/data_source_cloudstack_network_offering.go b/cloudstack/data_source_cloudstack_network_offering.go index b7801a03..8e57fae2 100644 --- a/cloudstack/data_source_cloudstack_network_offering.go +++ b/cloudstack/data_source_cloudstack_network_offering.go @@ -138,8 +138,8 @@ func applyNetworkOfferingFilters(networkOffering *cloudstack.NetworkOffering, fi return false, fmt.Errorf("Invalid regex: %s", err) } updatedName := strings.ReplaceAll(m["name"].(string), "_", "") - sshKeyPairField := networkOfferingJSON[updatedName].(string) - if !r.MatchString(sshKeyPairField) { + networkOfferingField := networkOfferingJSON[updatedName].(string) + if !r.MatchString(networkOfferingField) { return false, nil }