diff --git a/cloudstack/data_source_cloudstack_instance.go b/cloudstack/data_source_cloudstack_instance.go new file mode 100644 index 00000000..a78f39b1 --- /dev/null +++ b/cloudstack/data_source_cloudstack_instance.go @@ -0,0 +1,208 @@ +// +// 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 dataSourceCloudstackInstance() *schema.Resource { + return &schema.Resource{ + Read: dataSourceCloudstackInstanceRead, + Schema: map[string]*schema.Schema{ + "filter": dataSourceFiltersSchema(), + + //Computed values + "instance_id": { + Type: schema.TypeString, + Computed: true, + }, + + "account": { + 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, + }, + + "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, + }, + }, + }, + }, + }, + } +} + +func dataSourceCloudstackInstanceRead(d *schema.ResourceData, meta interface{}) error { + log.Printf("Instance Data Source Read Started") + + 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 + + //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 { + if ip_address == i.Nic[0].Ipaddress { + 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) + } + } + } + + 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 + } + 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) + d.Set("nic", []interface{}{map[string]string{"ip_address": instance.Nic[0].Ipaddress}}) + + 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 +} diff --git a/cloudstack/data_source_cloudstack_instance_test.go b/cloudstack/data_source_cloudstack_instance_test.go new file mode 100644 index 00000000..e27b1e14 --- /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/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" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccInstanceDataSourceConfig_basic, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(datasourceName, "display_name", resourceName, "display_name"), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +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 + ] + } +` 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/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}" + } + ` diff --git a/cloudstack/data_source_cloudstack_network_offering.go b/cloudstack/data_source_cloudstack_network_offering.go new file mode 100644 index 00000000..8e57fae2 --- /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 network offering from the list of filtered network offerings 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 a network offering: %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), "_", "") + networkOfferingField := networkOfferingJSON[updatedName].(string) + if !r.MatchString(networkOfferingField) { + return false, nil + } + + } + return true, nil +} 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 + ] + } + ` 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/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 + ] + } + ` 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/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 + ] + + } + ` 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/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/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/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 + ] + } + ` 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/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}" +} + ` 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/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/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 + ] + + } + ` diff --git a/cloudstack/provider.go b/cloudstack/provider.go index 36d4c736..0cf6ab9e 100644 --- a/cloudstack/provider.go +++ b/cloudstack/provider.go @@ -78,7 +78,17 @@ func Provider() terraform.ResourceProvider { }, DataSourcesMap: map[string]*schema.Resource{ - "cloudstack_template": dataSourceCloudstackTemplate(), + "cloudstack_template": dataSourceCloudstackTemplate(), + "cloudstack_ssh_keypair": dataSourceCloudstackSSHKeyPair(), + "cloudstack_instance": dataSourceCloudstackInstance(), + "cloudstack_network_offering": dataSourceCloudstackNetworkOffering(), + "cloudstack_zone": dataSourceCloudStackZone(), + "cloudstack_service_offering": dataSourceCloudstackServiceOffering(), + "cloudstack_volume": dataSourceCloudstackVolume(), + "cloudstack_vpc": dataSourceCloudstackVPC(), + "cloudstack_ipaddress": dataSourceCloudstackIPAddress(), + "cloudstack_user": dataSourceCloudstackUser(), + "cloudstack_vpn_connection": dataSourceCloudstackVPNConnection(), }, ResourcesMap: map[string]*schema.Resource{ @@ -107,6 +117,14 @@ func Provider() terraform.ResourceProvider { "cloudstack_vpn_connection": resourceCloudStackVPNConnection(), "cloudstack_vpn_customer_gateway": resourceCloudStackVPNCustomerGateway(), "cloudstack_vpn_gateway": resourceCloudStackVPNGateway(), + "cloudstack_network_offering": resourceCloudStackNetworkOffering(), + "cloudstack_disk_offering": resourceCloudStackDiskOffering(), + "cloudstack_volume": resourceCloudStackVolume(), + "cloudstack_zone": resourceCloudStackZone(), + "cloudstack_service_offering": resourceCloudStackServiceOffering(), + "cloudstack_account": resourceCloudStackAccount(), + "cloudstack_user": resourceCloudStackUser(), + "cloudstack_domain": resourceCloudStackDomain(), }, ConfigureFunc: providerConfigure, diff --git a/cloudstack/resource_cloudstack_account.go b/cloudstack/resource_cloudstack_account.go new file mode 100644 index 00000000..a0d950f9 --- /dev/null +++ b/cloudstack/resource_cloudstack_account.go @@ -0,0 +1,123 @@ +// +// 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 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, + }, + "account": { + Type: schema.TypeString, + Optional: 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) + 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", account) + a, err := cs.Account.CreateAccount(p) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Account %s successfully created", account) + d.SetId(a.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 { + 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 +} diff --git a/cloudstack/resource_cloudstack_disk_offering.go b/cloudstack/resource_cloudstack_disk_offering.go new file mode 100644 index 00000000..daa6b889 --- /dev/null +++ b/cloudstack/resource_cloudstack_disk_offering.go @@ -0,0 +1,79 @@ +// +// 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 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.TypeInt, + Required: true, + }, + }, + } +} + +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 } + +func resourceCloudStackDiskOfferingUpdate(d *schema.ResourceData, meta interface{}) error { return nil } + +func resourceCloudStackDiskOfferingDelete(d *schema.ResourceData, meta interface{}) error { return nil } 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 +} diff --git a/cloudstack/resource_cloudstack_network_offering.go b/cloudstack/resource_cloudstack_network_offering.go new file mode 100644 index 00000000..e11ece26 --- /dev/null +++ b/cloudstack/resource_cloudstack_network_offering.go @@ -0,0 +1,214 @@ +// +// 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 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 { + 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 Network Offering: %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 +} diff --git a/cloudstack/resource_cloudstack_service_offering.go b/cloudstack/resource_cloudstack_service_offering.go new file mode 100644 index 00000000..ad887b13 --- /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_text": { + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +func resourceCloudStackServiceOfferingCreate(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + name := d.Get("name").(string) + display_text := d.Get("display_text").(string) + + // Create a new parameter struct + p := cs.ServiceOffering.NewCreateServiceOfferingParams(display_text, 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_text", 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 +} diff --git a/cloudstack/resource_cloudstack_user.go b/cloudstack/resource_cloudstack_user.go new file mode 100644 index 00000000..aabb7b20 --- /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 resourceCloudStackUserRead(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 +} 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 +} 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 +}