From 0edf7ddd115862a4a81a945ac751aca0990e6368 Mon Sep 17 00:00:00 2001 From: ArthurVerrept Date: Tue, 12 Mar 2024 15:45:07 +0000 Subject: [PATCH 01/12] add storage disks --- docs/resources/storage_disk.md | 26 ++ internal/provider/provider.go | 2 + internal/provider/provider_test.go | 1 - internal/provider/storage_disk_data_source.go | 112 ++++++ .../provider/storage_disk_data_source_test.go | 71 ++++ internal/provider/storage_disk_resource.go | 346 ++++++++++++++++++ .../provider/storage_disk_resource_test.go | 63 ++++ internal/provider/vm_resource.go | 66 +++- internal/provider/vm_resource_test.go | 100 +++++ 9 files changed, 766 insertions(+), 21 deletions(-) create mode 100644 docs/resources/storage_disk.md create mode 100644 internal/provider/storage_disk_data_source.go create mode 100644 internal/provider/storage_disk_data_source_test.go create mode 100644 internal/provider/storage_disk_resource.go create mode 100644 internal/provider/storage_disk_resource_test.go diff --git a/docs/resources/storage_disk.md b/docs/resources/storage_disk.md new file mode 100644 index 0000000..7d7d77b --- /dev/null +++ b/docs/resources/storage_disk.md @@ -0,0 +1,26 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "cudo_storage_disk Resource - terraform-provider-cudo" +subcategory: "" +description: |- + Storage disk resource +--- + +# cudo_storage_disk (Resource) + +Storage disk resource + + + + +## Schema + +### Required + +- `data_center_id` (String) The unique identifier of the datacenter where the disk is located. +- `id` (String) The unique identifier of the storage disk +- `size_gib` (Number) Size of the storage disk in GiB + +### Optional + +- `project_id` (String) The project the storage disk is in. diff --git a/internal/provider/provider.go b/internal/provider/provider.go index bd05e8e..de7507d 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -198,6 +198,7 @@ func (p *CudoProvider) Configure(ctx context.Context, req provider.ConfigureRequ func (p *CudoProvider) Resources(ctx context.Context) []func() resource.Resource { return []func() resource.Resource{ NewSecurityGroupResource, + NewStorageDiskResource, NewNetworkResource, NewVMImageResource, NewVMResource, @@ -206,6 +207,7 @@ func (p *CudoProvider) Resources(ctx context.Context) []func() resource.Resource func (p *CudoProvider) DataSources(ctx context.Context) []func() datasource.DataSource { return []func() datasource.DataSource{ + NewStorageDiskDataSource, NewVMImagesDataSource, NewVMDataCentersDataSource, NewVMDataSource, diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go index d77e4c4..c1991ad 100644 --- a/internal/provider/provider_test.go +++ b/internal/provider/provider_test.go @@ -29,7 +29,6 @@ var testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServe } func getProviderConfig() string { - return fmt.Sprintf(` provider "cudo" { api_key = "%s" diff --git a/internal/provider/storage_disk_data_source.go b/internal/provider/storage_disk_data_source.go new file mode 100644 index 0000000..a29202e --- /dev/null +++ b/internal/provider/storage_disk_data_source.go @@ -0,0 +1,112 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/CudoVentures/terraform-provider-cudo/internal/compute/vm" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ datasource.DataSource = &StorageDiskDataSource{} + +func NewStorageDiskDataSource() datasource.DataSource { + return &StorageDiskDataSource{} +} + +// SecurityGroupsDataSource defines the data source implementation. +type StorageDiskDataSource struct { + client *CudoClientData +} + +// SecurityGroupDataSourceModel describes the resource data model. +type StorageDiskDataSourceModel struct { + ID types.String `tfsdk:"id"` + ProjectID types.String `tfsdk:"project_id"` + DataCenterID types.String `tfsdk:"data_center_id"` + SizeGib types.Int64 `tfsdk:"size_gib"` +} + +func (d *StorageDiskDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = "cudo_storage_disk" +} + +func (d *StorageDiskDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Disk data source", + Description: "Gets a Disk", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "Storage disk ID.", + Required: true, + }, + "project_id": schema.StringAttribute{ + Description: "The unique identifier of the project the disk is in.", + Optional: true, + }, + "data_center_id": schema.StringAttribute{ + Description: "The unique identifier of the datacenter where the disk is located.", + Computed: true, + }, + "size_gib": schema.Int64Attribute{ + Description: "Size of the storage disk in GiB", + Computed: true, + }, + }, + } +} + +func (d *StorageDiskDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*CudoClientData) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *CudoClient, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + d.client = client +} + +func (d *StorageDiskDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var state StorageDiskDataSourceModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &state)...) + + if resp.Diagnostics.HasError() { + return + } + + projectId := d.client.DefaultProjectID + if !state.ProjectID.IsNull() { + projectId = state.ProjectID.ValueString() + } + + res, err := d.client.VMClient.GetDisk(ctx, &vm.GetDiskRequest{ + ProjectId: projectId, + Id: state.ID.ValueString(), + }) + if err != nil { + resp.Diagnostics.AddError( + "Unable to read storage disks", + err.Error(), + ) + return + } + + state.DataCenterID = types.StringValue(res.Disk.DataCenterId) + state.SizeGib = types.Int64Value(int64(res.Disk.SizeGib)) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} diff --git a/internal/provider/storage_disk_data_source_test.go b/internal/provider/storage_disk_data_source_test.go new file mode 100644 index 0000000..123119c --- /dev/null +++ b/internal/provider/storage_disk_data_source_test.go @@ -0,0 +1,71 @@ +package provider + +import ( + "context" + "fmt" + "testing" + + "github.com/CudoVentures/terraform-provider-cudo/internal/compute/vm" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +func TestAcc_StorageDiskDataSource(t *testing.T) { + var cancel context.CancelFunc + ctx := context.Background() + deadline, ok := t.Deadline() + if ok { + ctx, cancel = context.WithDeadline(ctx, deadline) + defer cancel() + } + name := "tf-ds-test-" + testRunID + + resourcesConfig := fmt.Sprintf(` +resource "cudo_storage_disk" "disk" { + data_center_id = "black-mesa" + id = "%s" + size_gib = 15 +}`, name) + + testAccStorageDiskDataSourceConfig := fmt.Sprintf(` +data "cudo_storage_disk" "test" { + id = "%s" +}`, name) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: func(state *terraform.State) error { + cl, _ := getClients(t) + + ins, err := cl.GetDisk(ctx, &vm.GetDiskRequest{ + Id: name, + ProjectId: projectID, + }) + + if err == nil && ins.Disk.DiskState != vm.Disk_DISK_STATE_DELETE { + res, err := cl.DeleteStorageDisk(ctx, &vm.DeleteStorageDiskRequest{ + Id: name, + ProjectId: projectID, + }) + t.Logf("(%s) %#v: %v", ins.Disk.DiskState, res, err) + + return fmt.Errorf("disk resource not destroyed %s , %s", ins.Disk.Id, ins.Disk.DiskState) + } + return nil + }, + + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: getProviderConfig() + resourcesConfig, + }, + { + Config: getProviderConfig() + resourcesConfig + testAccStorageDiskDataSourceConfig, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.cudo_storage_disk.test", "id", name), + resource.TestCheckResourceAttr("data.cudo_storage_disk.test", "size_gib", "15"), + ), + }, + }, + }) +} diff --git a/internal/provider/storage_disk_resource.go b/internal/provider/storage_disk_resource.go new file mode 100644 index 0000000..354d0ce --- /dev/null +++ b/internal/provider/storage_disk_resource.go @@ -0,0 +1,346 @@ +package provider + +import ( + "context" + "fmt" + "regexp" + "time" + + "github.com/CudoVentures/terraform-provider-cudo/internal/compute/vm" + "github.com/CudoVentures/terraform-provider-cudo/internal/helper" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "google.golang.org/grpc/codes" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &StorageDiskResource{} +var _ resource.ResourceWithImportState = &StorageDiskResource{} + +func NewStorageDiskResource() resource.Resource { + return &StorageDiskResource{} +} + +// DiskResource defines the resource implementation. +type StorageDiskResource struct { + client *CudoClientData +} + +// SecurityGroupResourceModel describes the resource data model. +type StorageDiskResourceModel struct { + ProjectID types.String `tfsdk:"project_id"` + DataCenterID types.String `tfsdk:"data_center_id"` + Id types.String `tfsdk:"id"` + SizeGib types.Int64 `tfsdk:"size_gib"` +} + +func (r *StorageDiskResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "cudo_storage_disk" +} + +func (r *StorageDiskResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: "Storage disk resource", + Attributes: map[string]schema.Attribute{ + "data_center_id": schema.StringAttribute{ + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + MarkdownDescription: "The unique identifier of the datacenter where the disk is located.", + Required: true, + Validators: []validator.String{stringvalidator.RegexMatches( + regexp.MustCompile("^[a-z]([a-z0-9-]{0,61}[a-z0-9])?$"), "must be a valid resource id")}, + }, + "project_id": schema.StringAttribute{ + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + MarkdownDescription: "The project the storage disk is in.", + Optional: true, + }, + "id": schema.StringAttribute{ + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Description: "The unique identifier of the storage disk", + Required: true, + Validators: []validator.String{stringvalidator.RegexMatches( + regexp.MustCompile("^[a-z]([a-z0-9-]{0,61}[a-z0-9])?$"), "must be a valid resource id")}, + }, + "size_gib": schema.Int64Attribute{ + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.RequiresReplace(), + }, + Description: "Size of the storage disk in GiB", + Required: true, + }, + }, + } +} + +func (r *StorageDiskResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*CudoClientData) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *CudoClientData got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.client = client +} + +func (r *StorageDiskResource) waitForDiskDelete(ctx context.Context, diskID, projectID string) error { + refreshFunc := func() (interface{}, string, error) { + res, err := r.client.VMClient.GetDisk(ctx, &vm.GetDiskRequest{ + Id: diskID, + ProjectId: projectID, + }) + if err != nil { + // if not found resource has been deleted + if ok := helper.IsErrCode(err, codes.NotFound); ok { + // tflog.Debug(ctx, fmt.Sprintf("VM %s in project %s not found: ", vmID, projectID)) + return res, "done", nil + } + return nil, "", err + } + + // tflog.Trace(ctx, fmt.Sprintf("pending VM %s in project %s state: %s", vmID, projectID, res.Payload.VM.ShortState)) + return res, res.Disk.DiskState.String(), nil + } + + tflog.Debug(ctx, fmt.Sprintf("waiting for Disk %s in project %s ", diskID, projectID)) + + pendingStates := []string{ + vm.Disk_DISK_STATE_INIT.String(), + vm.Disk_DISK_STATE_READY.String(), + vm.Disk_DISK_STATE_USED.String(), + vm.Disk_DISK_STATE_DISABLED.String(), + vm.Disk_DISK_STATE_LOCKED.String(), + vm.Disk_DISK_STATE_ERROR.String(), + vm.Disk_DISK_STATE_CLONE.String(), + vm.Disk_DISK_STATE_DELETE.String(), + } + stateConf := &helper.StateChangeConf{ + Pending: pendingStates, + Target: []string{"done"}, + Refresh: refreshFunc, + Timeout: 20 * time.Minute, + Delay: 1 * time.Second, + MinTimeout: 3 * time.Second, + PollInterval: 5 * time.Second, + } + + _, err := stateConf.WaitForState(ctx) + if err != nil { + return err + } + + return nil +} + +func (r *StorageDiskResource) waitForDiskCreate(ctx context.Context, diskID, projectID string) error { + refreshFunc := func() (interface{}, string, error) { + res, err := r.client.VMClient.GetDisk(ctx, &vm.GetDiskRequest{ + ProjectId: projectID, + Id: diskID, + }) + if err != nil { + // if not found assume resource is initializing + if ok := helper.IsErrCode(err, codes.NotFound); ok { + // tflog.Debug(ctx, fmt.Sprintf("VM %s in project %s not found: ", vmID, projectID)) + return res, vm.Disk_DISK_STATE_INIT.String(), nil + } + return nil, "", err + } + + // tflog.Trace(ctx, fmt.Sprintf("pending VM %s in project %s state: %s", vmID, projectID, res.Payload.VM.ShortState)) + return res, res.Disk.DiskState.String(), nil + } + + tflog.Debug(ctx, fmt.Sprintf("waiting for Disk %s in project %s ", diskID, projectID)) + + pendingStates := []string{ + vm.Disk_DISK_STATE_INIT.String(), + vm.Disk_DISK_STATE_USED.String(), + vm.Disk_DISK_STATE_DISABLED.String(), + vm.Disk_DISK_STATE_LOCKED.String(), + vm.Disk_DISK_STATE_ERROR.String(), + vm.Disk_DISK_STATE_CLONE.String(), + vm.Disk_DISK_STATE_DELETE.String(), + } + + stateConf := &helper.StateChangeConf{ + Pending: pendingStates, + Target: []string{vm.Disk_DISK_STATE_READY.String()}, + Refresh: refreshFunc, + Timeout: 20 * time.Minute, + Delay: 1 * time.Second, + MinTimeout: 3 * time.Second, + PollInterval: 5 * time.Second, + } + + _, err := stateConf.WaitForState(ctx) + if err != nil { + return err + } + + return nil +} + +func (r *StorageDiskResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var state StorageDiskResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + projectID := r.client.DefaultProjectID + if !state.ProjectID.IsNull() { + projectID = state.ProjectID.ValueString() + } + + _, err := r.client.VMClient.CreateStorageDisk(ctx, &vm.CreateStorageDiskRequest{ + DataCenterId: state.DataCenterID.ValueString(), + ProjectId: projectID, + Disk: &vm.Disk{ + Id: string(state.Id.ValueString()), + SizeGib: int32(state.SizeGib.ValueInt64()), + }, + }) + if err != nil { + resp.Diagnostics.AddError( + "Unable to create storage disk resource", + err.Error(), + ) + return + } + + if err := r.waitForDiskCreate(ctx, state.Id.ValueString(), projectID); err != nil { + resp.Diagnostics.AddError( + "Unable to wait for Disk resource to be created", + err.Error(), + ) + return + } + + // state.DataCenterID = types.StringValue() + // state.Id = types.StringPointerValue(params.Body.Disk.ID) + // state.SizeGib = types.Int64Value(int64(*params.Body.Disk.SizeGib)) + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *StorageDiskResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state *StorageDiskResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + projectId := r.client.DefaultProjectID + if !state.ProjectID.IsNull() { + projectId = state.ProjectID.ValueString() + } + + res, err := r.client.VMClient.GetDisk(ctx, &vm.GetDiskRequest{ + Id: state.Id.ValueString(), + ProjectId: projectId, + }) + if err != nil { + if helper.IsErrCode(err, codes.NotFound) { + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError( + "Unable to read disk resource", + err.Error(), + ) + return + } + + state.DataCenterID = types.StringValue(res.Disk.DataCenterId) + state.Id = types.StringValue(res.Disk.Id) + state.SizeGib = types.Int64Value(int64(res.Disk.SizeGib)) + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *StorageDiskResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan StorageDiskResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + resp.Diagnostics.AddError( + "Error getting storage disk plan", + "Error getting storage disk plan", + ) + return + } + + // Read Terraform state data into the model + var state StorageDiskResourceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *StorageDiskResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state *StorageDiskResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + + projectId := r.client.DefaultProjectID + if !state.ProjectID.IsNull() { + projectId = state.ProjectID.ValueString() + } + + _, err := r.client.VMClient.DeleteStorageDisk(ctx, &vm.DeleteStorageDiskRequest{ + ProjectId: projectId, + Id: state.Id.ValueString(), + }) + + if err != nil { + resp.Diagnostics.AddError( + "Unable to delete storage disk", + err.Error(), + ) + return + } + + if err := r.waitForDiskDelete(ctx, state.Id.ValueString(), projectId); err != nil { + resp.Diagnostics.AddError( + "Unable to wait for Disk resource to be deleted", + err.Error(), + ) + return + } + + tflog.Trace(ctx, "deleted storage disk") +} + +func (r *StorageDiskResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} diff --git a/internal/provider/storage_disk_resource_test.go b/internal/provider/storage_disk_resource_test.go new file mode 100644 index 0000000..1a82b92 --- /dev/null +++ b/internal/provider/storage_disk_resource_test.go @@ -0,0 +1,63 @@ +package provider + +import ( + "context" + "fmt" + "testing" + + "github.com/CudoVentures/terraform-provider-cudo/internal/compute/vm" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +func TestAcc_StorageDiskResource(t *testing.T) { + var cancel context.CancelFunc + ctx := context.Background() + deadline, ok := t.Deadline() + if ok { + ctx, cancel = context.WithDeadline(ctx, deadline) + defer cancel() + } + name := "disk-resource-" + testRunID + + diskConf := fmt.Sprintf(` +resource "cudo_storage_disk" "disk" { + data_center_id = "black-mesa" + id = "%s" + size_gib = 15 +}`, name) + + resource.ParallelTest(t, resource.TestCase{ + CheckDestroy: func(state *terraform.State) error { + cl, _ := getClients(t) + + ins, err := cl.GetDisk(ctx, &vm.GetDiskRequest{ + Id: name, + ProjectId: projectID, + }) + if err == nil && ins.Disk.DiskState != vm.Disk_DISK_STATE_DELETE { + res, err := cl.DeleteStorageDisk(ctx, &vm.DeleteStorageDiskRequest{ + Id: name, + ProjectId: projectID, + }) + t.Logf("(%s) %#v: %v", ins.Disk.DiskState, res, err) + + return fmt.Errorf("disk resource not destroyed %s , %s", ins.Disk.Id, ins.Disk.DiskState) + } + return nil + }, + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: getProviderConfig() + diskConf, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("cudo_storage_disk.disk", "data_center_id", "black-mesa"), + resource.TestCheckResourceAttr("cudo_storage_disk.disk", "size_gib", "15"), + resource.TestCheckResourceAttr("cudo_storage_disk.disk", "id", name), + ), + }, + }, + }) +} diff --git a/internal/provider/vm_resource.go b/internal/provider/vm_resource.go index 9a697c2..292b9b3 100644 --- a/internal/provider/vm_resource.go +++ b/internal/provider/vm_resource.go @@ -227,6 +227,23 @@ func (r *VMResource) Schema(ctx context.Context, req resource.SchemaRequest, res MarkdownDescription: "A script to run when VM boots", Optional: true, }, + "storage_disks": schema.ListNestedAttribute{ + MarkdownDescription: "Specification for storage disks", + Optional: true, + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "disk_id": schema.StringAttribute{ + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + MarkdownDescription: "ID of storage disk to attach to vm", + Required: true, + Validators: []validator.String{stringvalidator.RegexMatches(regexp.MustCompile("^[a-z]([a-z0-9-]{0,61}[a-z0-9])?$"), "must be a valid resource id")}, + }, + }, + }, + }, "vcpus": schema.Int64Attribute{ PlanModifiers: []planmodifier.Int64{ int64planmodifier.RequiresReplace(), @@ -246,26 +263,27 @@ type VMResource struct { // VMResourceModel describes the resource data model. type VMResourceModel struct { - BootDisk *VMBootDiskResourceModel `tfsdk:"boot_disk"` - DataCenterID types.String `tfsdk:"data_center_id"` - CPUModel types.String `tfsdk:"cpu_model"` - GPUs types.Int64 `tfsdk:"gpus"` - GPUModel types.String `tfsdk:"gpu_model"` - ID types.String `tfsdk:"id"` - MachineType types.String `tfsdk:"machine_type"` - MemoryGib types.Int64 `tfsdk:"memory_gib"` - Password types.String `tfsdk:"password"` - PriceHr types.String `tfsdk:"price_hr"` - ProjectID types.String `tfsdk:"project_id"` - SSHKeys []types.String `tfsdk:"ssh_keys"` - SSHKeySource types.String `tfsdk:"ssh_key_source"` - StartScript types.String `tfsdk:"start_script"` - VCPUs types.Int64 `tfsdk:"vcpus"` - Networks []*VMNICResourceModel `tfsdk:"networks"` - InternalIPAddress types.String `tfsdk:"internal_ip_address"` - ExternalIPAddress types.String `tfsdk:"external_ip_address"` - RenewableEnergy types.Bool `tfsdk:"renewable_energy"` - SecurityGroupIDs types.Set `tfsdk:"security_group_ids"` + BootDisk *VMBootDiskResourceModel `tfsdk:"boot_disk"` + DataCenterID types.String `tfsdk:"data_center_id"` + CPUModel types.String `tfsdk:"cpu_model"` + GPUs types.Int64 `tfsdk:"gpus"` + GPUModel types.String `tfsdk:"gpu_model"` + ID types.String `tfsdk:"id"` + MachineType types.String `tfsdk:"machine_type"` + MemoryGib types.Int64 `tfsdk:"memory_gib"` + Password types.String `tfsdk:"password"` + PriceHr types.String `tfsdk:"price_hr"` + ProjectID types.String `tfsdk:"project_id"` + SSHKeys []types.String `tfsdk:"ssh_keys"` + SSHKeySource types.String `tfsdk:"ssh_key_source"` + StartScript types.String `tfsdk:"start_script"` + StorageDisks []*VMStorageDiskResourceModel `tfsdk:"storage_disks"` + VCPUs types.Int64 `tfsdk:"vcpus"` + Networks []*VMNICResourceModel `tfsdk:"networks"` + InternalIPAddress types.String `tfsdk:"internal_ip_address"` + ExternalIPAddress types.String `tfsdk:"external_ip_address"` + RenewableEnergy types.Bool `tfsdk:"renewable_energy"` + SecurityGroupIDs types.Set `tfsdk:"security_group_ids"` Metadata types.Map `tfsdk:"metadata"` } @@ -282,6 +300,10 @@ type VMNICResourceModel struct { SecurityGroupIDs types.Set `tfsdk:"security_group_ids"` } +type VMStorageDiskResourceModel struct { + DiskID types.String `tfsdk:"disk_id"` +} + func (r *VMResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { // Prevent panic if the provider has not been configured. if req.ProviderData == nil { @@ -359,6 +381,9 @@ func (r *VMResource) Create(ctx context.Context, req resource.CreateRequest, res return } + storageDiskIds := make([]string, len(state.StorageDisks)) + for i, diskResource := range state.StorageDisks { + storageDiskIds[i] = diskResource.DiskID.ValueString() metadataMap := make(map[string]string) diag := state.Metadata.ElementsAs(ctx, &metadataMap, false) if diag.HasError() { @@ -382,6 +407,7 @@ func (r *VMResource) Create(ctx context.Context, req resource.CreateRequest, res SshKeySource: sshKeySource, CustomSshKeys: customKeys, StartScript: state.StartScript.ValueString(), + StorageDiskIds: storageDiskIds, Metadata: metadataMap, } diff --git a/internal/provider/vm_resource_test.go b/internal/provider/vm_resource_test.go index 691df80..fb88536 100644 --- a/internal/provider/vm_resource_test.go +++ b/internal/provider/vm_resource_test.go @@ -266,3 +266,103 @@ resource "cudo_vm" "vm-oob-delete" { }, }) } + +func TestAcc_VMStorageDiskResource(t *testing.T) { + var cancel context.CancelFunc + ctx := context.Background() + deadline, ok := t.Deadline() + if ok { + ctx, cancel = context.WithDeadline(ctx, deadline) + defer cancel() + } + name := "vm-resource-" + testRunID + diskName1 := "vm-resource-disk1" + testRunID + diskName2 := "vm-resource-disk2" + testRunID + + vmConfig := fmt.Sprintf(` +resource "cudo_storage_disk" "my-storage-disk" { + data_center_id = "black-mesa" + id = "%s" + size_gib = 10 +} + +resource "cudo_storage_disk" "my-storage-disk2" { + data_center_id = "black-mesa" + id = "%s" + size_gib = 20 +} + +resource "cudo_vm" "vm" { + depends_on = [cudo_storage_disk.my-storage-disk, cudo_storage_disk.my-storage-disk2] + machine_type = "standard" + data_center_id = "black-mesa" + vcpus = 1 + boot_disk = { + image_id = "alpine-linux-317" + size_gib = 1 + } + memory_gib = 2 + id = "%s" + networks = [ + { + network_id = "tf-test" + } + ] + storage_disks = [ + { + disk_id = "%s" + }, + { + disk_id = "%s" + } + ] + }`, diskName1, diskName2, name, diskName1, diskName2) + + resource.ParallelTest(t, resource.TestCase{ + CheckDestroy: func(state *terraform.State) error { + cl, _ := getClients(t) + + getParams := &vm.GetVMRequest{ + Id: name, + ProjectId: projectID, + } + + getRes, err := cl.GetVM(ctx, getParams) + if err == nil && getRes.VM.ShortState != "epil" { + terminateParams := &vm.TerminateVMRequest{ + Id: name, + ProjectId: projectID, + } + res, err := cl.TerminateVM(ctx, terminateParams) + t.Logf("(%s) %#v: %v", getRes.VM.ShortState, res, err) + + return fmt.Errorf("vm resource not destroyed %s , %s", getRes.VM.Id, getRes.VM.ShortState) + } + return nil + }, + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: getProviderConfig() + vmConfig, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("cudo_vm.vm", "boot_disk.image_id", "alpine-linux-317"), + resource.TestCheckResourceAttr("cudo_vm.vm", "boot_disk.size_gib", "1"), + resource.TestCheckResourceAttr("cudo_vm.vm", "machine_type", "standard"), + resource.TestCheckResourceAttr("cudo_vm.vm", "cpu_model", "Haswell-noTSX-IBRS"), + resource.TestCheckResourceAttr("cudo_vm.vm", "data_center_id", "black-mesa"), + resource.TestCheckResourceAttr("cudo_vm.vm", "gpu_model", ""), + resource.TestCheckResourceAttr("cudo_vm.vm", "memory_gib", "2"), + resource.TestCheckResourceAttr("cudo_vm.vm", "storage_disks.0.disk_id", diskName1), + resource.TestCheckResourceAttr("cudo_vm.vm", "storage_disks.1.disk_id", diskName2), + resource.TestCheckResourceAttrSet("cudo_vm.vm", "price_hr"), + resource.TestCheckResourceAttrSet("cudo_vm.vm", "internal_ip_address"), + resource.TestCheckResourceAttr("cudo_vm.vm", "renewable_energy", "true"), + resource.TestCheckResourceAttr("cudo_vm.vm", "vcpus", "1"), + resource.TestCheckResourceAttr("cudo_vm.vm", "id", name), + ), + }, + }, + }) +} From 1f06b1a23a6831c5c4b6e198f2f000a6a2e2da1f Mon Sep 17 00:00:00 2001 From: ArthurVerrept Date: Tue, 12 Mar 2024 15:57:34 +0000 Subject: [PATCH 02/12] update disks in append --- internal/provider/vm_resource.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/internal/provider/vm_resource.go b/internal/provider/vm_resource.go index 292b9b3..55e338c 100644 --- a/internal/provider/vm_resource.go +++ b/internal/provider/vm_resource.go @@ -629,6 +629,19 @@ func appendVmState(vm *vm.VM, state *VMResourceModel) { nic.ExternalIPAddress = types.StringValue(vm.Nics[i].ExternalIpAddress) nic.InternalIPAddress = types.StringValue(vm.Nics[i].InternalIpAddress) } + for _, vmDisk := range vm.StorageDisks { + isPresent := false + for _, stateDisk := range state.StorageDisks { + if vmDisk.Id == stateDisk.DiskID.ValueString() { + isPresent = true + } + } + if !isPresent { + state.StorageDisks = append(state.StorageDisks, &VMStorageDiskResourceModel{ + DiskID: types.StringValue(vmDisk.Id), + }) + } + } state.GPUModel = types.StringValue(vm.GpuModel) state.ID = types.StringValue(vm.Id) state.InternalIPAddress = types.StringValue(vm.InternalIpAddress) From dbd7537d9c504bf7bf1de948454a17773d916049 Mon Sep 17 00:00:00 2001 From: ArthurVerrept Date: Thu, 14 Mar 2024 09:48:13 +0000 Subject: [PATCH 03/12] disks as nested set for order changes --- internal/provider/vm_resource.go | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/internal/provider/vm_resource.go b/internal/provider/vm_resource.go index 55e338c..9fba1b6 100644 --- a/internal/provider/vm_resource.go +++ b/internal/provider/vm_resource.go @@ -227,7 +227,7 @@ func (r *VMResource) Schema(ctx context.Context, req resource.SchemaRequest, res MarkdownDescription: "A script to run when VM boots", Optional: true, }, - "storage_disks": schema.ListNestedAttribute{ + "storage_disks": schema.SetNestedAttribute{ MarkdownDescription: "Specification for storage disks", Optional: true, Computed: true, @@ -428,6 +428,7 @@ func (r *VMResource) Create(ctx context.Context, req resource.CreateRequest, res ) return } + appendVmState(vm.VM, &state) resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) } @@ -629,19 +630,13 @@ func appendVmState(vm *vm.VM, state *VMResourceModel) { nic.ExternalIPAddress = types.StringValue(vm.Nics[i].ExternalIpAddress) nic.InternalIPAddress = types.StringValue(vm.Nics[i].InternalIpAddress) } - for _, vmDisk := range vm.StorageDisks { - isPresent := false - for _, stateDisk := range state.StorageDisks { - if vmDisk.Id == stateDisk.DiskID.ValueString() { - isPresent = true - } - } - if !isPresent { - state.StorageDisks = append(state.StorageDisks, &VMStorageDiskResourceModel{ - DiskID: types.StringValue(vmDisk.Id), - }) + storageDisks := make([]*VMStorageDiskResourceModel, len(vm.StorageDisks)) + for i, vmDisk := range vm.StorageDisks { + storageDisks[i] = &VMStorageDiskResourceModel{ + DiskID: types.StringValue(vmDisk.Id), } } + state.StorageDisks = storageDisks state.GPUModel = types.StringValue(vm.GpuModel) state.ID = types.StringValue(vm.Id) state.InternalIPAddress = types.StringValue(vm.InternalIpAddress) From e610fd8fe5b2cf69b97931915b35a1d210399956 Mon Sep 17 00:00:00 2001 From: ArthurVerrept Date: Thu, 14 Mar 2024 10:57:32 +0000 Subject: [PATCH 04/12] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d661623..8ede3bf 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ See docs directory If you wish to work on the provider, you'll first need [Go](http://www.golang.org) installed on your machine (see [Requirements](#requirements) above). -The Cudo API client is generated in the cudo-compute-market repo using the "make tf" command, copy the generated "compute" folder from cudo-compute-market/clients/go-grpc/ to terraform-provider-cudo/internal/. +The Cudo API client is generated in the cudo-compute-market repo using the `make tf` command, copy the generated "compute" folder from cudo-compute-market/clients/go-grpc/ to terraform-provider-cudo/internal/. To compile the provider, run `go install`. This will build the provider and put the provider binary in the `$GOPATH/bin` directory. From 36a9b9cac0a0fb5167d07be511a801c95864e3cf Mon Sep 17 00:00:00 2001 From: ArthurVerrept Date: Thu, 14 Mar 2024 12:56:04 +0000 Subject: [PATCH 05/12] fix disk set problems on non disk vm's --- internal/provider/vm_resource.go | 9 ++++----- internal/provider/vm_resource_test.go | 5 +++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/provider/vm_resource.go b/internal/provider/vm_resource.go index 9fba1b6..2fef422 100644 --- a/internal/provider/vm_resource.go +++ b/internal/provider/vm_resource.go @@ -230,7 +230,6 @@ func (r *VMResource) Schema(ctx context.Context, req resource.SchemaRequest, res "storage_disks": schema.SetNestedAttribute{ MarkdownDescription: "Specification for storage disks", Optional: true, - Computed: true, NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ "disk_id": schema.StringAttribute{ @@ -630,11 +629,11 @@ func appendVmState(vm *vm.VM, state *VMResourceModel) { nic.ExternalIPAddress = types.StringValue(vm.Nics[i].ExternalIpAddress) nic.InternalIPAddress = types.StringValue(vm.Nics[i].InternalIpAddress) } - storageDisks := make([]*VMStorageDiskResourceModel, len(vm.StorageDisks)) - for i, vmDisk := range vm.StorageDisks { - storageDisks[i] = &VMStorageDiskResourceModel{ + var storageDisks []*VMStorageDiskResourceModel + for _, vmDisk := range vm.StorageDisks { + storageDisks = append(storageDisks, &VMStorageDiskResourceModel{ DiskID: types.StringValue(vmDisk.Id), - } + }) } state.StorageDisks = storageDisks state.GPUModel = types.StringValue(vm.GpuModel) diff --git a/internal/provider/vm_resource_test.go b/internal/provider/vm_resource_test.go index fb88536..21e3ed5 100644 --- a/internal/provider/vm_resource_test.go +++ b/internal/provider/vm_resource_test.go @@ -276,8 +276,9 @@ func TestAcc_VMStorageDiskResource(t *testing.T) { defer cancel() } name := "vm-resource-" + testRunID - diskName1 := "vm-resource-disk1" + testRunID - diskName2 := "vm-resource-disk2" + testRunID + // important that the disks are in ascending order as that is how the api returns them + diskName1 := "vm-resource-disk1" + diskName2 := "vm-resource-disk2" vmConfig := fmt.Sprintf(` resource "cudo_storage_disk" "my-storage-disk" { From 3abfcf24aca2b77629d0bbd33a22bca164d4c7b7 Mon Sep 17 00:00:00 2001 From: ArthurVerrept Date: Thu, 14 Mar 2024 12:58:42 +0000 Subject: [PATCH 06/12] fix tests --- .../provider/storage_disk_data_source_test.go | 10 ++-- .../provider/storage_disk_resource_test.go | 8 +-- internal/provider/vm_resource_test.go | 51 ++++++++++--------- 3 files changed, 35 insertions(+), 34 deletions(-) diff --git a/internal/provider/storage_disk_data_source_test.go b/internal/provider/storage_disk_data_source_test.go index 123119c..0f52c3f 100644 --- a/internal/provider/storage_disk_data_source_test.go +++ b/internal/provider/storage_disk_data_source_test.go @@ -18,17 +18,17 @@ func TestAcc_StorageDiskDataSource(t *testing.T) { ctx, cancel = context.WithDeadline(ctx, deadline) defer cancel() } - name := "tf-ds-test-" + testRunID + name := "storage-disk-data-source" + testRunID resourcesConfig := fmt.Sprintf(` -resource "cudo_storage_disk" "disk" { +resource "cudo_storage_disk" "disk_ds" { data_center_id = "black-mesa" id = "%s" size_gib = 15 }`, name) testAccStorageDiskDataSourceConfig := fmt.Sprintf(` -data "cudo_storage_disk" "test" { +data "cudo_storage_disk" "storage_disk_datasource" { id = "%s" }`, name) @@ -62,8 +62,8 @@ data "cudo_storage_disk" "test" { { Config: getProviderConfig() + resourcesConfig + testAccStorageDiskDataSourceConfig, Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("data.cudo_storage_disk.test", "id", name), - resource.TestCheckResourceAttr("data.cudo_storage_disk.test", "size_gib", "15"), + resource.TestCheckResourceAttr("data.cudo_storage_disk.storage_disk_datasource", "id", name), + resource.TestCheckResourceAttr("data.cudo_storage_disk.storage_disk_datasource", "size_gib", "15"), ), }, }, diff --git a/internal/provider/storage_disk_resource_test.go b/internal/provider/storage_disk_resource_test.go index 1a82b92..6542e19 100644 --- a/internal/provider/storage_disk_resource_test.go +++ b/internal/provider/storage_disk_resource_test.go @@ -21,7 +21,7 @@ func TestAcc_StorageDiskResource(t *testing.T) { name := "disk-resource-" + testRunID diskConf := fmt.Sprintf(` -resource "cudo_storage_disk" "disk" { +resource "cudo_storage_disk" "storage_disk_resource" { data_center_id = "black-mesa" id = "%s" size_gib = 15 @@ -53,9 +53,9 @@ resource "cudo_storage_disk" "disk" { { Config: getProviderConfig() + diskConf, Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("cudo_storage_disk.disk", "data_center_id", "black-mesa"), - resource.TestCheckResourceAttr("cudo_storage_disk.disk", "size_gib", "15"), - resource.TestCheckResourceAttr("cudo_storage_disk.disk", "id", name), + resource.TestCheckResourceAttr("cudo_storage_disk.storage_disk_resource", "data_center_id", "black-mesa"), + resource.TestCheckResourceAttr("cudo_storage_disk.storage_disk_resource", "size_gib", "15"), + resource.TestCheckResourceAttr("cudo_storage_disk.storage_disk_resource", "id", name), ), }, }, diff --git a/internal/provider/vm_resource_test.go b/internal/provider/vm_resource_test.go index 21e3ed5..a77c239 100644 --- a/internal/provider/vm_resource_test.go +++ b/internal/provider/vm_resource_test.go @@ -275,26 +275,27 @@ func TestAcc_VMStorageDiskResource(t *testing.T) { ctx, cancel = context.WithDeadline(ctx, deadline) defer cancel() } - name := "vm-resource-" + testRunID + name := "vm-storage-disk-resource-" + testRunID // important that the disks are in ascending order as that is how the api returns them - diskName1 := "vm-resource-disk1" - diskName2 := "vm-resource-disk2" + diskName1 := "vm-resource-disk1" + testRunID + diskName2 := "vm-resource-disk2" + testRunID - vmConfig := fmt.Sprintf(` -resource "cudo_storage_disk" "my-storage-disk" { + diskConfig := fmt.Sprintf(` +resource "cudo_storage_disk" "vm_resource_disk1" { data_center_id = "black-mesa" id = "%s" size_gib = 10 } -resource "cudo_storage_disk" "my-storage-disk2" { +resource "cudo_storage_disk" "vm_resource_disk2" { data_center_id = "black-mesa" id = "%s" size_gib = 20 -} +}`, diskName1, diskName2) -resource "cudo_vm" "vm" { - depends_on = [cudo_storage_disk.my-storage-disk, cudo_storage_disk.my-storage-disk2] + vmConfig := fmt.Sprintf(` +resource "cudo_vm" "vm_disk_resource" { + depends_on = [cudo_storage_disk.vm_resource_disk1, cudo_storage_disk.vm_resource_disk2] machine_type = "standard" data_center_id = "black-mesa" vcpus = 1 @@ -317,7 +318,7 @@ resource "cudo_vm" "vm" { disk_id = "%s" } ] - }`, diskName1, diskName2, name, diskName1, diskName2) + }`, name, diskName1, diskName2) resource.ParallelTest(t, resource.TestCase{ CheckDestroy: func(state *terraform.State) error { @@ -346,22 +347,22 @@ resource "cudo_vm" "vm" { Steps: []resource.TestStep{ // Create and Read testing { - Config: getProviderConfig() + vmConfig, + Config: getProviderConfig() + diskConfig + vmConfig, Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("cudo_vm.vm", "boot_disk.image_id", "alpine-linux-317"), - resource.TestCheckResourceAttr("cudo_vm.vm", "boot_disk.size_gib", "1"), - resource.TestCheckResourceAttr("cudo_vm.vm", "machine_type", "standard"), - resource.TestCheckResourceAttr("cudo_vm.vm", "cpu_model", "Haswell-noTSX-IBRS"), - resource.TestCheckResourceAttr("cudo_vm.vm", "data_center_id", "black-mesa"), - resource.TestCheckResourceAttr("cudo_vm.vm", "gpu_model", ""), - resource.TestCheckResourceAttr("cudo_vm.vm", "memory_gib", "2"), - resource.TestCheckResourceAttr("cudo_vm.vm", "storage_disks.0.disk_id", diskName1), - resource.TestCheckResourceAttr("cudo_vm.vm", "storage_disks.1.disk_id", diskName2), - resource.TestCheckResourceAttrSet("cudo_vm.vm", "price_hr"), - resource.TestCheckResourceAttrSet("cudo_vm.vm", "internal_ip_address"), - resource.TestCheckResourceAttr("cudo_vm.vm", "renewable_energy", "true"), - resource.TestCheckResourceAttr("cudo_vm.vm", "vcpus", "1"), - resource.TestCheckResourceAttr("cudo_vm.vm", "id", name), + resource.TestCheckResourceAttr("cudo_vm.vm_disk_resource", "boot_disk.image_id", "alpine-linux-317"), + resource.TestCheckResourceAttr("cudo_vm.vm_disk_resource", "boot_disk.size_gib", "1"), + resource.TestCheckResourceAttr("cudo_vm.vm_disk_resource", "machine_type", "standard"), + resource.TestCheckResourceAttr("cudo_vm.vm_disk_resource", "cpu_model", "Haswell-noTSX-IBRS"), + resource.TestCheckResourceAttr("cudo_vm.vm_disk_resource", "data_center_id", "black-mesa"), + resource.TestCheckResourceAttr("cudo_vm.vm_disk_resource", "gpu_model", ""), + resource.TestCheckResourceAttr("cudo_vm.vm_disk_resource", "memory_gib", "2"), + resource.TestCheckResourceAttr("cudo_vm.vm_disk_resource", "storage_disks.0.disk_id", diskName1), + resource.TestCheckResourceAttr("cudo_vm.vm_disk_resource", "storage_disks.1.disk_id", diskName2), + resource.TestCheckResourceAttrSet("cudo_vm.vm_disk_resource", "price_hr"), + resource.TestCheckResourceAttrSet("cudo_vm.vm_disk_resource", "internal_ip_address"), + resource.TestCheckResourceAttr("cudo_vm.vm_disk_resource", "renewable_energy", "true"), + resource.TestCheckResourceAttr("cudo_vm.vm_disk_resource", "vcpus", "1"), + resource.TestCheckResourceAttr("cudo_vm.vm_disk_resource", "id", name), ), }, }, From 1acb8fc6051e732f406a055169c62b3c4433b891 Mon Sep 17 00:00:00 2001 From: ArthurVerrept Date: Thu, 14 Mar 2024 17:14:59 +0000 Subject: [PATCH 07/12] docs --- docs/data-sources/storage_disk.md | 29 +++++++++++++++++++++++++++++ docs/resources/vm.md | 9 +++++++++ 2 files changed, 38 insertions(+) create mode 100644 docs/data-sources/storage_disk.md diff --git a/docs/data-sources/storage_disk.md b/docs/data-sources/storage_disk.md new file mode 100644 index 0000000..2e5ea70 --- /dev/null +++ b/docs/data-sources/storage_disk.md @@ -0,0 +1,29 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "cudo_storage_disk Data Source - terraform-provider-cudo" +subcategory: "" +description: |- + Disk data source +--- + +# cudo_storage_disk (Data Source) + +Disk data source + + + + +## Schema + +### Required + +- `id` (String) Storage disk ID. + +### Optional + +- `project_id` (String) The unique identifier of the project the disk is in. + +### Read-Only + +- `data_center_id` (String) The unique identifier of the datacenter where the disk is located. +- `size_gib` (Number) Size of the storage disk in GiB diff --git a/docs/resources/vm.md b/docs/resources/vm.md index cc46b60..0df2d5d 100644 --- a/docs/resources/vm.md +++ b/docs/resources/vm.md @@ -74,6 +74,7 @@ resource "cudo_vm" "my-vm" { - `ssh_key_source` (String) Which SSH keys to add to the VM: project (default), user or custom - `ssh_keys` (List of String) List of SSH keys to add to the VM, ssh_key_source must be set to custom - `start_script` (String) A script to run when VM boots +- `storage_disks` (Attributes Set) Specification for storage disks (see [below for nested schema](#nestedatt--storage_disks)) - `vcpus` (Number) Number of VCPUs ### Read-Only @@ -111,3 +112,11 @@ Read-Only: - `external_ip_address` (String) The external IP address of the NIC. - `internal_ip_address` (String) The internal IP address of the NIC. + + + +### Nested Schema for `storage_disks` + +Required: + +- `disk_id` (String) ID of storage disk to attach to vm From 5ba3c0c22e65a460d91d05bb3d8666025393e50a Mon Sep 17 00:00:00 2001 From: ArthurVerrept Date: Tue, 2 Apr 2024 12:29:19 +0100 Subject: [PATCH 08/12] fix after rebase --- internal/provider/vm_resource.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/provider/vm_resource.go b/internal/provider/vm_resource.go index 2fef422..c880eb2 100644 --- a/internal/provider/vm_resource.go +++ b/internal/provider/vm_resource.go @@ -283,7 +283,7 @@ type VMResourceModel struct { ExternalIPAddress types.String `tfsdk:"external_ip_address"` RenewableEnergy types.Bool `tfsdk:"renewable_energy"` SecurityGroupIDs types.Set `tfsdk:"security_group_ids"` - Metadata types.Map `tfsdk:"metadata"` + Metadata types.Map `tfsdk:"metadata"` } type VMBootDiskResourceModel struct { @@ -383,6 +383,8 @@ func (r *VMResource) Create(ctx context.Context, req resource.CreateRequest, res storageDiskIds := make([]string, len(state.StorageDisks)) for i, diskResource := range state.StorageDisks { storageDiskIds[i] = diskResource.DiskID.ValueString() + } + metadataMap := make(map[string]string) diag := state.Metadata.ElementsAs(ctx, &metadataMap, false) if diag.HasError() { From e75c5d2dd28b69f6dda7c015a7b5ce030bee282a Mon Sep 17 00:00:00 2001 From: ArthurVerrept Date: Tue, 2 Apr 2024 13:10:23 +0100 Subject: [PATCH 09/12] check for nil resources --- internal/provider/network_resource.go | 8 +++++--- internal/provider/storage_disk_resource.go | 4 ---- internal/provider/vm_resource.go | 5 ++++- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/internal/provider/network_resource.go b/internal/provider/network_resource.go index fc3b4ed..ae99866 100644 --- a/internal/provider/network_resource.go +++ b/internal/provider/network_resource.go @@ -145,9 +145,11 @@ func (r *NetworkResource) Create(ctx context.Context, req resource.CreateRequest return } - state.Gateway = types.StringValue(network.Gateway) - state.ExternalIPAddress = types.StringValue(network.ExternalIpAddress) - state.InternalIPAddress = types.StringValue(network.InternalIpAddress) + if network != nil { + state.Gateway = types.StringValue(network.Gateway) + state.ExternalIPAddress = types.StringValue(network.ExternalIpAddress) + state.InternalIPAddress = types.StringValue(network.InternalIpAddress) + } resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) } diff --git a/internal/provider/storage_disk_resource.go b/internal/provider/storage_disk_resource.go index 354d0ce..298e879 100644 --- a/internal/provider/storage_disk_resource.go +++ b/internal/provider/storage_disk_resource.go @@ -242,10 +242,6 @@ func (r *StorageDiskResource) Create(ctx context.Context, req resource.CreateReq return } - // state.DataCenterID = types.StringValue() - // state.Id = types.StringPointerValue(params.Body.Disk.ID) - // state.SizeGib = types.Int64Value(int64(*params.Body.Disk.SizeGib)) - resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) } diff --git a/internal/provider/vm_resource.go b/internal/provider/vm_resource.go index c880eb2..dc30777 100644 --- a/internal/provider/vm_resource.go +++ b/internal/provider/vm_resource.go @@ -430,7 +430,10 @@ func (r *VMResource) Create(ctx context.Context, req resource.CreateRequest, res return } - appendVmState(vm.VM, &state) + // if the vm is created and returned update the state. + if vm != nil && vm.VM != nil { + appendVmState(vm.VM, &state) + } resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) } From 8e80d194dece86a764f2da6c83ed62e5ad9c5490 Mon Sep 17 00:00:00 2001 From: ArthurVerrept Date: Wed, 10 Apr 2024 15:41:23 +0100 Subject: [PATCH 10/12] gen docs --- docs/resources/vm.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/resources/vm.md b/docs/resources/vm.md index 0df2d5d..8af8cde 100644 --- a/docs/resources/vm.md +++ b/docs/resources/vm.md @@ -64,7 +64,6 @@ resource "cudo_vm" "my-vm" { - `gpu_model` (String) The model of the GPU. - `gpus` (Number) Number of GPUs - `machine_type` (String) VM machine type, from machine type data source -- `max_price_hr` (String) The maximum price per hour for the VM instance - `memory_gib` (Number) Amount of VM memory in GiB - `metadata` (Map of String) Metadata values to associate with the VM instance - `networks` (Attributes List) Network adapters for private networks (see [below for nested schema](#nestedatt--networks)) From a1354a57677eeef0c8dc47b3e122d6fcd8ae8d14 Mon Sep 17 00:00:00 2001 From: ArthurVerrept Date: Thu, 11 Apr 2024 10:15:26 +0100 Subject: [PATCH 11/12] update example --- docs/resources/vm.md | 12 ++++++++++++ examples/resources/cudo_vm/resource.tf | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/docs/resources/vm.md b/docs/resources/vm.md index 8af8cde..490d751 100644 --- a/docs/resources/vm.md +++ b/docs/resources/vm.md @@ -30,8 +30,15 @@ resource "cudo_vm" "my-vm-max-price" { ] } +resource "cudo_storage_disk" "my-storage-disk" { + data_center_id = "gb-bournemouth-1" + id = "my-disk" + size_gib = 100 +} + # pick a specific data center and machine type resource "cudo_vm" "my-vm" { + depends_on = [cudo_storage_disk.my-storage-disk] id = "terra-vm-1" machine_type = "standard" data_center_id = "gb-bournemouth-1" @@ -41,6 +48,11 @@ resource "cudo_vm" "my-vm" { image_id = "debian-11" size_gib = 50 } + storage_disks = [ + { + disk_id = "my-disk" + } + ] ssh_key_source = "project" start_script = < Date: Thu, 11 Apr 2024 10:19:34 +0100 Subject: [PATCH 12/12] add storage disk examples --- docs/data-sources/storage_disk.md | 6 ++++++ docs/resources/storage_disk.md | 10 +++++++++- examples/data-sources/cudo_storage_disk/data-source.tf | 3 +++ examples/resources/cudo_storage_disk/resource.tf | 5 +++++ 4 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 examples/data-sources/cudo_storage_disk/data-source.tf create mode 100644 examples/resources/cudo_storage_disk/resource.tf diff --git a/docs/data-sources/storage_disk.md b/docs/data-sources/storage_disk.md index 2e5ea70..57725da 100644 --- a/docs/data-sources/storage_disk.md +++ b/docs/data-sources/storage_disk.md @@ -10,7 +10,13 @@ description: |- Disk data source +## Example Usage +```terraform +data "cudo_storage_disk" "storage_disk_datasource" { + id = "my-disk" +} +``` ## Schema diff --git a/docs/resources/storage_disk.md b/docs/resources/storage_disk.md index 7d7d77b..39bcbeb 100644 --- a/docs/resources/storage_disk.md +++ b/docs/resources/storage_disk.md @@ -10,7 +10,15 @@ description: |- Storage disk resource - +## Example Usage + +```terraform +resource "cudo_storage_disk" "my-storage-disk" { + data_center_id = "gb-bournemouth-1" + id = "my-disk" + size_gib = 100 +} +``` ## Schema diff --git a/examples/data-sources/cudo_storage_disk/data-source.tf b/examples/data-sources/cudo_storage_disk/data-source.tf new file mode 100644 index 0000000..5595074 --- /dev/null +++ b/examples/data-sources/cudo_storage_disk/data-source.tf @@ -0,0 +1,3 @@ +data "cudo_storage_disk" "storage_disk_datasource" { + id = "my-disk" +} \ No newline at end of file diff --git a/examples/resources/cudo_storage_disk/resource.tf b/examples/resources/cudo_storage_disk/resource.tf new file mode 100644 index 0000000..7c48074 --- /dev/null +++ b/examples/resources/cudo_storage_disk/resource.tf @@ -0,0 +1,5 @@ +resource "cudo_storage_disk" "my-storage-disk" { + data_center_id = "gb-bournemouth-1" + id = "my-disk" + size_gib = 100 +} \ No newline at end of file