From 7bd9338a2722737d6c8beb3733d66005d8c57ddd Mon Sep 17 00:00:00 2001 From: Uzair Ali <72073401+uzaxirr@users.noreply.github.com> Date: Tue, 8 Oct 2024 01:59:29 +0530 Subject: [PATCH 1/8] misc corrections and conflits --- civo/instances/datasource_instance.go | 26 ++++++++++++++ civo/instances/resource_instance.go | 41 +++++++++++++++++++++-- civo/volume/resource_volume_attachment.go | 34 +++++++++++++++++-- 3 files changed, 96 insertions(+), 5 deletions(-) diff --git a/civo/instances/datasource_instance.go b/civo/instances/datasource_instance.go index 332412aa..fef25748 100644 --- a/civo/instances/datasource_instance.go +++ b/civo/instances/datasource_instance.go @@ -102,6 +102,20 @@ func DataSourceInstance() *schema.Resource { Computed: true, Description: "An optional list of tags", }, + "attached_volume": { + Type: schema.TypeList, + Optional: true, + Description: "A list of volumes to attached at boot to the instance.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Required: true, + Description: "The ID of the volume to attach.", + }, + }, + }, + }, "script": { Type: schema.TypeString, Computed: true, @@ -192,5 +206,17 @@ func dataSourceInstanceRead(_ context.Context, d *schema.ResourceData, m interfa d.Set("created_at", foundImage.CreatedAt.UTC().String()) d.Set("notes", foundImage.Notes) + if len(foundImage.AttachedVolumes) > 0 { + volumes := make([]map[string]interface{}, 0, len(foundImage.AttachedVolumes)) + for _, volume := range foundImage.AttachedVolumes { + volumeMap := map[string]interface{}{ + "id": volume.ID, + } + volumes = append(volumes, volumeMap) + } + + d.Set("attached_volume", volumes) + } + return nil } diff --git a/civo/instances/resource_instance.go b/civo/instances/resource_instance.go index 7d406489..7d0cf37f 100644 --- a/civo/instances/resource_instance.go +++ b/civo/instances/resource_instance.go @@ -106,6 +106,20 @@ func ResourceInstance() *schema.Resource { Description: "An optional list of tags, represented as a key, value pair", Elem: &schema.Schema{Type: schema.TypeString}, }, + "attached_volume": { + Type: schema.TypeList, + Optional: true, + Description: "A list of volumes to attached at boot to the instance.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Required: true, + Description: "The ID of the volume to attach.", + }, + }, + }, + }, "script": { Type: schema.TypeString, Optional: true, @@ -287,6 +301,18 @@ func resourceInstanceCreate(ctx context.Context, d *schema.ResourceData, m inter config.Tags = tags + tfVolumeAttach := d.Get("attached_volume").([]interface{}) + volumes := make([]civogo.AttachedVolume, 0, len(tfVolumeAttach)) + for _, v := range tfVolumeAttach { + volumeData := v.(map[string]interface{}) + volumes = append(volumes, civogo.AttachedVolume{ + ID: volumeData["id"].(string), + }) + } + if len(volumes) > 0 { + config.AttachedVolumes = volumes + } + log.Printf("[INFO] creating the instance %s", d.Get("hostname").(string)) instance, err := apiClient.CreateInstance(config) @@ -295,8 +321,7 @@ func resourceInstanceCreate(ctx context.Context, d *schema.ResourceData, m inter if parseErr == nil { err = customErr } - // quota errors introduce new line after each missing quota, causing formatting issues: - return diag.Errorf("[ERR] failed to create instance: %s", strings.ReplaceAll(err.Error(), "\n", " ")) + return diag.Errorf("[ERR] failed to create instance: %s", err) } d.SetId(instance.ID) @@ -375,6 +400,18 @@ func resourceInstanceRead(_ context.Context, d *schema.ResourceData, m interface d.Set("initial_password", "") } + if len(resp.AttachedVolumes) > 0 { + volumes := make([]map[string]interface{}, 0, len(resp.AttachedVolumes)) + for _, volume := range resp.AttachedVolumes { + volumeMap := map[string]interface{}{ + "id": volume.ID, + } + volumes = append(volumes, volumeMap) + } + + d.Set("attached_volume", volumes) + } + if resp.Script == "" { d.Set("script", "") } diff --git a/civo/volume/resource_volume_attachment.go b/civo/volume/resource_volume_attachment.go index 1c1f86ef..c390bad5 100644 --- a/civo/volume/resource_volume_attachment.go +++ b/civo/volume/resource_volume_attachment.go @@ -33,6 +33,12 @@ func ResourceVolumeAttachment() *schema.Resource { ValidateFunc: validation.NoZeroValues, Description: "The ID of target volume for attachment", }, + "attach_at_boot": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + Description: "The volume is attach at boot time", + }, "region": { Type: schema.TypeString, Optional: true, @@ -108,12 +114,22 @@ func resourceVolumeAttachmentCreate(ctx context.Context, d *schema.ResourceData, MinTimeout: 3 * time.Second, NotFoundChecks: 10, } - _, err = createStateConf.WaitForStateContext(context.Background()) + if attachAtBoot { + createStateConf.Pending = []string{"available", "attaching"} + createStateConf.Target = []string{"attaching"} + } + + ctx, cancel := context.WithTimeout(context.Background(), createStateConf.Timeout) + defer cancel() + + _, err = createStateConf.WaitForStateContext(ctx) if err != nil { return diag.Errorf("error waiting for volume (%s) to be attached: %s", d.Id(), err) } - return resourceVolumeAttachmentRead(ctx, d, m) + ret := resourceVolumeAttachmentRead(ctx, d, m) + diags = append(diags, ret...) + return diags } // function to read the volume @@ -150,6 +166,7 @@ func resourceVolumeAttachmentRead(_ context.Context, d *schema.ResourceData, m i // function to delete the volume func resourceVolumeAttachmentDelete(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { apiClient := m.(*civogo.Client) + var diags diag.Diagnostics // overwrite the region if it's defined if region, ok := d.GetOk("region"); ok { @@ -157,11 +174,22 @@ func resourceVolumeAttachmentDelete(_ context.Context, d *schema.ResourceData, m } volumeID := d.Get("volume_id").(string) + attachAtBoot := d.Get("attach_at_boot").(bool) + instanceID := d.Get("instance_id").(string) + + if attachAtBoot { + // Notify the terminal + msg := fmt.Sprintf("To use the volume %s, The instance %s needs to be rebooted", volumeID, instanceID) + diags = append(diags, diag.Diagnostic{ + Severity: diag.Warning, + Summary: msg, + }) + } log.Printf("[INFO] Detaching the volume %s", d.Id()) _, err := apiClient.DetachVolume(volumeID) if err != nil { return diag.Errorf("[ERR] an error occurred while trying to detach the volume %s", err) } - return nil + return diags } From dae141df9a341b1615ddeec3e7795437efab7e8e Mon Sep 17 00:00:00 2001 From: Vishal Anarase Date: Tue, 1 Oct 2024 15:36:43 +0530 Subject: [PATCH 2/8] Bumpup civogo Signed-off-by: Vishal Anarase --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3f40da68..319ad4c5 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/civo/terraform-provider-civo require ( - github.com/civo/civogo v0.3.84 + github.com/civo/civogo v0.3.79 github.com/google/uuid v1.3.1 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/terraform-plugin-sdk/v2 v2.31.0 diff --git a/go.sum b/go.sum index ca1af8e3..28bcceef 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmms github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/civo/civogo v0.3.84 h1:jf5IT7VJFPaReO6g8B0zqKhsYCIizaGo4PjDLY7Sl6Y= -github.com/civo/civogo v0.3.84/go.mod h1:7UCYX+qeeJbrG55E1huv+0ySxcHTqq/26FcHLVelQJM= +github.com/civo/civogo v0.3.79 h1:Z1MbEG9CsGqSZV7UFBA0xsjk7TBGUPHjW9sM7cS5yZM= +github.com/civo/civogo v0.3.79/go.mod h1:7UCYX+qeeJbrG55E1huv+0ySxcHTqq/26FcHLVelQJM= github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= From e6395d303b6fbf3ea8dcea02c97b4002bc429412 Mon Sep 17 00:00:00 2001 From: Uzair Ali <72073401+uzaxirr@users.noreply.github.com> Date: Tue, 8 Oct 2024 02:09:05 +0530 Subject: [PATCH 3/8] go mod --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 319ad4c5..3f40da68 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/civo/terraform-provider-civo require ( - github.com/civo/civogo v0.3.79 + github.com/civo/civogo v0.3.84 github.com/google/uuid v1.3.1 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/terraform-plugin-sdk/v2 v2.31.0 diff --git a/go.sum b/go.sum index 28bcceef..ca1af8e3 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmms github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/civo/civogo v0.3.79 h1:Z1MbEG9CsGqSZV7UFBA0xsjk7TBGUPHjW9sM7cS5yZM= -github.com/civo/civogo v0.3.79/go.mod h1:7UCYX+qeeJbrG55E1huv+0ySxcHTqq/26FcHLVelQJM= +github.com/civo/civogo v0.3.84 h1:jf5IT7VJFPaReO6g8B0zqKhsYCIizaGo4PjDLY7Sl6Y= +github.com/civo/civogo v0.3.84/go.mod h1:7UCYX+qeeJbrG55E1huv+0ySxcHTqq/26FcHLVelQJM= github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= From 61610a44f53548058e92c2c9052389c2b4114003 Mon Sep 17 00:00:00 2001 From: Uzair Ali <72073401+uzaxirr@users.noreply.github.com> Date: Tue, 8 Oct 2024 02:15:21 +0530 Subject: [PATCH 4/8] BUG --- civo/volume/resource_volume_attachment.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/civo/volume/resource_volume_attachment.go b/civo/volume/resource_volume_attachment.go index c390bad5..5647c596 100644 --- a/civo/volume/resource_volume_attachment.go +++ b/civo/volume/resource_volume_attachment.go @@ -91,7 +91,7 @@ func resourceVolumeAttachmentCreate(ctx context.Context, d *schema.ResourceData, } log.Printf("[INFO] attaching the volume %s to instance %s", volumeID, instanceID) - _, err := apiClient.AttachVolume(volumeID, vuc) + _, err = apiClient.AttachVolume(volumeID, vuc) if err != nil { return diag.Errorf("[ERR] error attaching volume to instance %s", err) } From 97503ad71e221814564c99a75a14e93730055174 Mon Sep 17 00:00:00 2001 From: Uzair Ali <72073401+uzaxirr@users.noreply.github.com> Date: Tue, 8 Oct 2024 13:44:58 +0530 Subject: [PATCH 5/8] solve bug --- civo/instances/resource_instance.go | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/civo/instances/resource_instance.go b/civo/instances/resource_instance.go index 7d0cf37f..709043ef 100644 --- a/civo/instances/resource_instance.go +++ b/civo/instances/resource_instance.go @@ -401,15 +401,29 @@ func resourceInstanceRead(_ context.Context, d *schema.ResourceData, m interface } if len(resp.AttachedVolumes) > 0 { - volumes := make([]map[string]interface{}, 0, len(resp.AttachedVolumes)) - for _, volume := range resp.AttachedVolumes { - volumeMap := map[string]interface{}{ - "id": volume.ID, + // Get the attached volumes from the API response + attachedVolumes := resp.AttachedVolumes + + // Get the attached volumes from the Terraform state + tfAttachedVolumes := d.Get("attached_volume").([]interface{}) + + // Create a map of volumes listed in the Terraform config for comparison + configVolumeMap := make(map[string]bool) + for _, v := range tfAttachedVolumes { + volume := v.(map[string]interface{}) + configVolumeMap[volume["id"].(string)] = true + } + + // Filter out API volumes that are not in the Terraform config + var filteredVolumes []civogo.AttachedVolume + for _, vol := range attachedVolumes { + if _, exists := configVolumeMap[vol.ID]; exists { + filteredVolumes = append(filteredVolumes, vol) } - volumes = append(volumes, volumeMap) } - d.Set("attached_volume", volumes) + // Set only the filtered volumes in the Terraform state + d.Set("attached_volume", filteredVolumes) } if resp.Script == "" { From 786e7f99812035bc95efae8beda1d98026c6ca8a Mon Sep 17 00:00:00 2001 From: Uzair Ali <72073401+uzaxirr@users.noreply.github.com> Date: Wed, 9 Oct 2024 13:34:11 +0530 Subject: [PATCH 6/8] Add default vol type --- civo/instances/resource_instance.go | 3 ++- civo/volume/resource_volume.go | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/civo/instances/resource_instance.go b/civo/instances/resource_instance.go index 709043ef..1104edd1 100644 --- a/civo/instances/resource_instance.go +++ b/civo/instances/resource_instance.go @@ -20,7 +20,7 @@ import ( ) // ResourceInstance The instance resource represents an object of type instances -// and with it you can handle the instances created with Terraform +// and with it, you can handle the instances created with Terraform func ResourceInstance() *schema.Resource { return &schema.Resource{ Description: "Provides a Civo instance resource. This can be used to create, modify, and delete instances.", @@ -98,6 +98,7 @@ func ResourceInstance() *schema.Resource { "volume_type": { Type: schema.TypeString, Optional: true, + Default: "ms-xfs-2-replicas", Description: "The type of volume to use, either 'ssd' or 'bssd' (optional; default 'ssd')", }, "tags": { diff --git a/civo/volume/resource_volume.go b/civo/volume/resource_volume.go index 6f8d6a81..fe6e7b2f 100644 --- a/civo/volume/resource_volume.go +++ b/civo/volume/resource_volume.go @@ -49,6 +49,7 @@ func ResourceVolume() *schema.Resource { "volume_type": { Type: schema.TypeString, Optional: true, + Default: "ms-xfs-2-replicas", Description: "The type of the volume", }, }, From cbeb42453ff62dc71c158ebede9a61e106f2f98a Mon Sep 17 00:00:00 2001 From: Uzair Ali <72073401+uzaxirr@users.noreply.github.com> Date: Wed, 9 Oct 2024 15:06:11 +0530 Subject: [PATCH 7/8] Add default vol type --- civo/volume/resource_volume.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/civo/volume/resource_volume.go b/civo/volume/resource_volume.go index fe6e7b2f..f8600a96 100644 --- a/civo/volume/resource_volume.go +++ b/civo/volume/resource_volume.go @@ -92,7 +92,7 @@ func resourceVolumeCreate(ctx context.Context, d *schema.ResourceData, m interfa volume, err := apiClient.NewVolume(config) if err != nil { - return diag.Errorf("[ERR] failed to create a new volume: %s", err) + return diag.Errorf("[ERR] failed to create a new volume: %s region: %s", err, apiClient.Region) } d.SetId(volume.ID) From 35f2b23b5d4a0f5293244d905e7ef18b0dfdb54a Mon Sep 17 00:00:00 2001 From: Uzair Ali <72073401+uzaxirr@users.noreply.github.com> Date: Mon, 14 Oct 2024 12:36:30 +0530 Subject: [PATCH 8/8] Support volume detachment upon instance update --- civo/instances/resource_instance.go | 51 +++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/civo/instances/resource_instance.go b/civo/instances/resource_instance.go index 1104edd1..ec10be75 100644 --- a/civo/instances/resource_instance.go +++ b/civo/instances/resource_instance.go @@ -96,9 +96,9 @@ func ResourceInstance() *schema.Resource { Description: "The ID of the firewall to use, from the current list. If left blank or not sent, the default firewall will be used (open to all)", }, "volume_type": { - Type: schema.TypeString, - Optional: true, - Default: "ms-xfs-2-replicas", + Type: schema.TypeString, + Optional: true, + //Default: "ms-xfs-2-replicas", Description: "The type of volume to use, either 'ssd' or 'bssd' (optional; default 'ssd')", }, "tags": { @@ -538,6 +538,51 @@ func resourceInstanceUpdate(ctx context.Context, d *schema.ResourceData, m inter } } + if d.HasChange("attached_volume") { + oldVolumes, newVolumes := d.GetChange("attached_volume") + oldVolumeList := oldVolumes.([]interface{}) + newVolumeList := newVolumes.([]interface{}) + + // Check if there are any new volumes being attached + for _, newVolume := range newVolumeList { + newVolumeData := newVolume.(map[string]interface{}) + found := false + for _, oldVolume := range oldVolumeList { + oldVolumeData := oldVolume.(map[string]interface{}) + if newVolumeData["id"] == oldVolumeData["id"] { + found = true + break + } + } + if !found { + // This is a new volume being attached, which is not allowed + return diag.Errorf("Attaching new volumes after instance creation is not allowed. Please create a new civo_volume_attachment resource for attaching additional volume.") + } + } + + // Handle volume detachments + for _, oldVolume := range oldVolumeList { + oldVolumeData := oldVolume.(map[string]interface{}) + found := false + for _, newVolume := range newVolumeList { + newVolumeData := newVolume.(map[string]interface{}) + if oldVolumeData["id"] == newVolumeData["id"] { + found = true + break + } + } + if !found { + // This volume is no longer in the config, so detach it + volumeID := oldVolumeData["id"].(string) + _, err := apiClient.DetachVolume(volumeID) + if err != nil { + return diag.Errorf("Error detaching volume %s: %s", volumeID, err) + } + log.Printf("[INFO] Successfully detached volume %s from instance %s", volumeID, d.Id()) + } + } + } + // If reserved_ipv4 has changed, update the instance with the new reserved IP if d.HasChange("reserved_ipv4") { oldReservedIP, newReservedIP := d.GetChange("reserved_ipv4")