diff --git a/cloudstack/provider.go b/cloudstack/provider.go index 72090147..7008f407 100644 --- a/cloudstack/provider.go +++ b/cloudstack/provider.go @@ -123,6 +123,7 @@ func Provider() *schema.Provider { "cloudstack_firewall": resourceCloudStackFirewall(), "cloudstack_host": resourceCloudStackHost(), "cloudstack_instance": resourceCloudStackInstance(), + "cloudstack_instance_group": resourceCloudStackInstanceGroup(), "cloudstack_ipaddress": resourceCloudStackIPAddress(), "cloudstack_kubernetes_cluster": resourceCloudStackKubernetesCluster(), "cloudstack_kubernetes_version": resourceCloudStackKubernetesVersion(), diff --git a/cloudstack/resource_cloudstack_instance_group.go b/cloudstack/resource_cloudstack_instance_group.go new file mode 100644 index 00000000..d991ca3b --- /dev/null +++ b/cloudstack/resource_cloudstack_instance_group.go @@ -0,0 +1,147 @@ +// +// 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" + "strings" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceCloudStackInstanceGroup() *schema.Resource { + return &schema.Resource{ + Create: resourceCloudStackInstanceGroupCreate, + Read: resourceCloudStackInstanceGroupRead, + Update: resourceCloudStackInstanceGroupUpdate, + Delete: resourceCloudStackInstanceGroupDelete, + Importer: &schema.ResourceImporter{ + State: importStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + + "project": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + }, + } +} + +func resourceCloudStackInstanceGroupCreate(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + name := d.Get("name").(string) + + // Create a new parameter struct + p := cs.VMGroup.NewCreateInstanceGroupParams(name) + + // If there is a project supplied, we retrieve and set the project id + if err := setProjectid(p, cs, d); err != nil { + return err + } + + log.Printf("[DEBUG] Creating instance group %s", name) + r, err := cs.VMGroup.CreateInstanceGroup(p) + if err != nil { + return err + } + + log.Printf("[DEBUG] Instance group %s successfully created", name) + d.SetId(r.Id) + + return resourceCloudStackInstanceGroupRead(d, meta) +} + +func resourceCloudStackInstanceGroupRead(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + log.Printf("[DEBUG] Retrieving instance group %s", d.Get("name").(string)) + + // Get the instance group details + ig, count, err := cs.VMGroup.GetInstanceGroupByID( + d.Id(), + cloudstack.WithProject(d.Get("project").(string)), + ) + if err != nil { + if count == 0 { + log.Printf("[DEBUG] Instance group %s does no longer exist", d.Get("name").(string)) + d.SetId("") + return nil + } + + return err + } + + d.Set("name", ig.Name) + setValueOrID(d, "project", ig.Project, ig.Projectid) + + return nil +} + +func resourceCloudStackInstanceGroupUpdate(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + if d.HasChange("name") { + name := d.Get("name").(string) + + // Create a new parameter struct + p := cs.VMGroup.NewUpdateInstanceGroupParams(d.Id()) + p.SetName(name) + + log.Printf("[DEBUG] Updating instance group %s", d.Id()) + _, err := cs.VMGroup.UpdateInstanceGroup(p) + if err != nil { + return fmt.Errorf("Error updating instance group: %s", err) + } + } + + return resourceCloudStackInstanceGroupRead(d, meta) +} + +func resourceCloudStackInstanceGroupDelete(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + // Create a new parameter struct + p := cs.VMGroup.NewDeleteInstanceGroupParams(d.Id()) + + // Delete the instance group + _, err := cs.VMGroup.DeleteInstanceGroup(p) + if err != nil { + // This is a very poor way to be told the ID does no longer exist :( + if strings.Contains(err.Error(), fmt.Sprintf( + "Invalid parameter id value=%s due to incorrect long value format, "+ + "or entity does not exist", d.Id())) { + return nil + } + + return fmt.Errorf("Error deleting instance group: %s", err) + } + + return nil +} diff --git a/cloudstack/resource_cloudstack_instance_group_test.go b/cloudstack/resource_cloudstack_instance_group_test.go new file mode 100644 index 00000000..3b543585 --- /dev/null +++ b/cloudstack/resource_cloudstack_instance_group_test.go @@ -0,0 +1,147 @@ +// +// 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" + "testing" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +func TestAccCloudStackInstanceGroup_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackInstanceGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackInstanceGroup_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackInstanceGroupExists("cloudstack_instance_group.foo"), + resource.TestCheckResourceAttr( + "cloudstack_instance_group.foo", "name", "terraform-group"), + ), + }, + }, + }) +} + +func TestAccCloudStackInstanceGroup_update(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackInstanceGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackInstanceGroup_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackInstanceGroupExists("cloudstack_instance_group.foo"), + resource.TestCheckResourceAttr( + "cloudstack_instance_group.foo", "name", "terraform-group"), + ), + }, + { + Config: testAccCloudStackInstanceGroup_renamed, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackInstanceGroupExists("cloudstack_instance_group.foo"), + resource.TestCheckResourceAttr( + "cloudstack_instance_group.foo", "name", "terraform-group-renamed"), + ), + }, + }, + }) +} + +func TestAccCloudStackInstanceGroup_import(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackInstanceGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackInstanceGroup_basic, + }, + { + ResourceName: "cloudstack_instance_group.foo", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckCloudStackInstanceGroupExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No instance group ID is set") + } + + cs := testAccProvider.Meta().(*cloudstack.CloudStackClient) + ig, _, err := cs.VMGroup.GetInstanceGroupByID(rs.Primary.ID) + if err != nil { + return err + } + + if ig.Id != rs.Primary.ID { + return fmt.Errorf("Instance group not found") + } + + return nil + } +} + +func testAccCheckCloudStackInstanceGroupDestroy(s *terraform.State) error { + cs := testAccProvider.Meta().(*cloudstack.CloudStackClient) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "cloudstack_instance_group" { + continue + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No instance group ID is set") + } + + _, _, err := cs.VMGroup.GetInstanceGroupByID(rs.Primary.ID) + if err == nil { + return fmt.Errorf("Instance group %s still exists", rs.Primary.ID) + } + } + + return nil +} + +const testAccCloudStackInstanceGroup_basic = ` +resource "cloudstack_instance_group" "foo" { + name = "terraform-group" +}` + +const testAccCloudStackInstanceGroup_renamed = ` +resource "cloudstack_instance_group" "foo" { + name = "terraform-group-renamed" +}` diff --git a/website/docs/r/instance_group.html.markdown b/website/docs/r/instance_group.html.markdown new file mode 100644 index 00000000..36ad58d2 --- /dev/null +++ b/website/docs/r/instance_group.html.markdown @@ -0,0 +1,61 @@ +--- +layout: "cloudstack" +page_title: "CloudStack: cloudstack_instance_group" +sidebar_current: "docs-cloudstack-resource-instance-group" +description: |- + Creates an instance group. +--- + +# cloudstack_instance_group + +Creates an instance group. Instance groups are a simple way to organize +instances; an instance joins a group by setting its `group` argument to the +group's name. + +## Example Usage + +```hcl +resource "cloudstack_instance_group" "web" { + name = "web-servers" +} + +resource "cloudstack_instance" "web" { + name = "web-01" + group = cloudstack_instance_group.web.name + service_offering = "small" + network_id = cloudstack_network.default.id + template = "CentOS 7.0" + zone = "zone-01" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the instance group. + +* `project` - (Optional) The name or ID of the project to create this instance + group in. Changing this forces a new resource to be created. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The id of the instance group. +* `name` - The name of the instance group. + +## Import + +Instance groups can be imported; use `` as the import ID. For +example: + +```shell +terraform import cloudstack_instance_group.web 6226ea4d-9cbe-4cc9-b30c-b9532146da5b +``` + +When importing into a project you need to prefix the import ID with the project name: + +```shell +terraform import cloudstack_instance_group.web my-project/6226ea4d-9cbe-4cc9-b30c-b9532146da5b +```