diff --git a/cloudstack/provider_v6.go b/cloudstack/provider_v6.go index 339e4b37..e2d73e80 100644 --- a/cloudstack/provider_v6.go +++ b/cloudstack/provider_v6.go @@ -147,7 +147,11 @@ func (p *CloudstackProvider) ConfigValidators(ctx context.Context) []provider.Co } func (p *CloudstackProvider) Resources(ctx context.Context) []func() resource.Resource { - return []func() resource.Resource{} + return []func() resource.Resource{ + NewserviceOfferingUnconstrainedResource, + NewserviceOfferingConstrainedResource, + NewserviceOfferingFixedResource, + } } func (p *CloudstackProvider) DataSources(ctx context.Context) []func() datasource.DataSource { diff --git a/cloudstack/service_offering_constrained_resource.go b/cloudstack/service_offering_constrained_resource.go new file mode 100644 index 00000000..52ac2ee7 --- /dev/null +++ b/cloudstack/service_offering_constrained_resource.go @@ -0,0 +1,265 @@ +package cloudstack + +import ( + "context" + "fmt" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int32planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +var ( + _ resource.Resource = &serviceOfferingConstrainedResource{} + _ resource.ResourceWithConfigure = &serviceOfferingConstrainedResource{} +) + +func NewserviceOfferingConstrainedResource() resource.Resource { + return &serviceOfferingConstrainedResource{} +} + +type serviceOfferingConstrainedResource struct { + client *cloudstack.CloudStackClient +} + +func (r *serviceOfferingConstrainedResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: serviceOfferingMergeCommonSchema(map[string]schema.Attribute{ + "cpu_speed": schema.Int32Attribute{ + Required: true, + PlanModifiers: []planmodifier.Int32{ + int32planmodifier.RequiresReplace(), + }, + }, + "max_cpu_number": schema.Int32Attribute{ + Required: true, + PlanModifiers: []planmodifier.Int32{ + int32planmodifier.RequiresReplace(), + }, + }, + "max_memory": schema.Int32Attribute{ + Required: true, + PlanModifiers: []planmodifier.Int32{ + int32planmodifier.RequiresReplace(), + }, + }, + "min_cpu_number": schema.Int32Attribute{ + Required: true, + PlanModifiers: []planmodifier.Int32{ + int32planmodifier.RequiresReplace(), + }, + }, + "min_memory": schema.Int32Attribute{ + Required: true, + PlanModifiers: []planmodifier.Int32{ + int32planmodifier.RequiresReplace(), + }, + }, + }), + } +} + +func (r *serviceOfferingConstrainedResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // + var plan serviceOfferingConstrainedResourceModel + var planDiskQosHypervisor ServiceOfferingDiskQosHypervisor + var planDiskOffering ServiceOfferingDiskOffering + var planDiskQosStorage ServiceOfferingDiskQosStorage + + // + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if !plan.ServiceOfferingDiskQosHypervisor.IsNull() { + resp.Diagnostics.Append(plan.ServiceOfferingDiskQosHypervisor.As(ctx, &planDiskQosHypervisor, basetypes.ObjectAsOptions{})...) + } + if !plan.ServiceOfferingDiskOffering.IsNull() { + resp.Diagnostics.Append(plan.ServiceOfferingDiskOffering.As(ctx, &planDiskOffering, basetypes.ObjectAsOptions{})...) + } + if !plan.ServiceOfferingDiskQosStorage.IsNull() { + resp.Diagnostics.Append(plan.ServiceOfferingDiskQosStorage.As(ctx, &planDiskQosStorage, basetypes.ObjectAsOptions{})...) + } + if resp.Diagnostics.HasError() { + return + } + + // common params + params := r.client.ServiceOffering.NewCreateServiceOfferingParams(plan.DisplayText.ValueString(), plan.Name.ValueString()) + plan.commonCreateParams(params) + planDiskQosHypervisor.commonCreateParams(params) + planDiskOffering.commonCreateParams(params) + planDiskQosStorage.commonCreateParams(params) + + // resource specific params + if !plan.CpuSpeed.IsNull() { + params.SetCpuspeed(int(plan.CpuSpeed.ValueInt32())) + } + if !plan.MaxCpuNumber.IsNull() { + params.SetMaxcpunumber(int(plan.MaxCpuNumber.ValueInt32())) + } + if !plan.MaxMemory.IsNull() { + params.SetMaxmemory(int(plan.MaxMemory.ValueInt32())) + } + if !plan.MinCpuNumber.IsNull() { + params.SetMincpunumber(int(plan.MinCpuNumber.ValueInt32())) + } + if !plan.MinMemory.IsNull() { + params.SetMinmemory(int(plan.MinMemory.ValueInt32())) + } + + // create offering + cs, err := r.client.ServiceOffering.CreateServiceOffering(params) + if err != nil { + resp.Diagnostics.AddError( + "Error creating service offering", + "Could not create constrained offering, unexpected error: "+err.Error(), + ) + return + } + + // + plan.Id = types.StringValue(cs.Id) + + // + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) +} + +func (r *serviceOfferingConstrainedResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // + var state serviceOfferingConstrainedResourceModel + var stateDiskQosHypervisor ServiceOfferingDiskQosHypervisor + var stateDiskOffering ServiceOfferingDiskOffering + var stateDiskQosStorage ServiceOfferingDiskQosStorage + + // + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if !state.ServiceOfferingDiskQosHypervisor.IsNull() { + resp.Diagnostics.Append(state.ServiceOfferingDiskQosHypervisor.As(ctx, &stateDiskQosHypervisor, basetypes.ObjectAsOptions{})...) + } + if !state.ServiceOfferingDiskOffering.IsNull() { + resp.Diagnostics.Append(state.ServiceOfferingDiskOffering.As(ctx, &stateDiskOffering, basetypes.ObjectAsOptions{})...) + } + if !state.ServiceOfferingDiskQosStorage.IsNull() { + resp.Diagnostics.Append(state.ServiceOfferingDiskQosStorage.As(ctx, &stateDiskQosStorage, basetypes.ObjectAsOptions{})...) + } + if resp.Diagnostics.HasError() { + return + } + + // + cs, _, err := r.client.ServiceOffering.GetServiceOfferingByID(state.Id.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "Error reading service offering", + "Could not read constrained service offering, unexpected error: "+err.Error(), + ) + return + } + + // resource specific + if cs.Cpuspeed > 0 { + state.CpuSpeed = types.Int32Value(int32(cs.Cpuspeed)) + } + // These fields arent returned from list + // max_cpu_number + // max_memory + // min_cpu_number + // min_memory + + // + state.commonRead(ctx, cs) + stateDiskQosHypervisor.commonRead(ctx, cs) + stateDiskOffering.commonRead(ctx, cs) + stateDiskQosStorage.commonRead(ctx, cs) + if resp.Diagnostics.HasError() { + return + } + + // + resp.Diagnostics.Append(resp.State.Set(ctx, state)...) + +} + +// Update updates the resource and sets the updated Terraform state on success. +func (r *serviceOfferingConstrainedResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // + var state serviceOfferingConstrainedResourceModel + + // + resp.Diagnostics.Append(req.Plan.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + // + params := r.client.ServiceOffering.NewUpdateServiceOfferingParams(state.Id.ValueString()) + state.commonUpdateParams(ctx, params) + + // + cs, err := r.client.ServiceOffering.UpdateServiceOffering(params) + if err != nil { + resp.Diagnostics.AddError( + "Error updating service offering", + "Could not update constrained service offering, unexpected error: "+err.Error(), + ) + return + } + + // + state.commonUpdate(ctx, cs) + if resp.Diagnostics.HasError() { + return + } + resp.Diagnostics.Append(resp.State.Set(ctx, state)...) +} + +// Delete deletes the resource and removes the Terraform state on success. +func (r *serviceOfferingConstrainedResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // + var state serviceOfferingConstrainedResourceModel + + // + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Delete the service offering + _, err := r.client.ServiceOffering.DeleteServiceOffering(r.client.ServiceOffering.NewDeleteServiceOfferingParams(state.Id.ValueString())) + if err != nil { + resp.Diagnostics.AddError( + "Error deleting service offering", + "Could not delete constrained offering, unexpected error: "+err.Error(), + ) + return + } +} + +// Configure adds the provider configured client to the resource. +func (r *serviceOfferingConstrainedResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Add a nil check when handling ProviderData because Terraform + // sets that data after it calls the ConfigureProvider RPC. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*cloudstack.CloudStackClient) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *cloudstack.CloudStackClient, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + r.client = client +} + +// Metadata returns the resource type name. +func (r *serviceOfferingConstrainedResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_service_offering_constrained" +} diff --git a/cloudstack/service_offering_constrained_resource_test.go b/cloudstack/service_offering_constrained_resource_test.go new file mode 100644 index 00000000..247c1560 --- /dev/null +++ b/cloudstack/service_offering_constrained_resource_test.go @@ -0,0 +1,313 @@ +// +// 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-plugin-testing/helper/resource" +) + +func TestAccServiceOfferingConstrained(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccMuxProvider, + Steps: []resource.TestStep{ + { + Config: testAccServiceOfferingCustomConstrained1, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("cloudstack_service_offering_constrained.constrained1", "name", "constrained1"), + ), + }, + { + Config: testAccServiceOfferingCustomConstrained1ZoneAll, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("cloudstack_service_offering_constrained.constrained1", "name", "constrained1"), + ), + }, + { + Config: testAccServiceOfferingCustomConstrained2, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("cloudstack_service_offering_constrained.constrained2", "name", "constrained2"), + ), + }, + { + Config: testAccServiceOfferingCustomConstrained2_update, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("cloudstack_service_offering_constrained.constrained2", "name", "constrained2update"), + ), + }, + { + Config: testAccServiceOfferingCustomConstrained_disk, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("cloudstack_service_offering_constrained.constrained1", "name", "constrained1"), + ), + }, + { + Config: testAccServiceOfferingCustomConstrained_disk_hypervisor, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("cloudstack_service_offering_constrained.disk_hypervisor", "name", "disk_hypervisor"), + ), + }, + { + Config: testAccServiceOfferingCustomConstrained_disk_storage, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("cloudstack_service_offering_constrained.disk_storage", "name", "disk_storage"), + ), + }, + }, + }) +} + +const testAccServiceOfferingCustomConstrained1 = ` +resource "cloudstack_service_offering_constrained" "constrained1" { + display_text = "constrained1" + name = "constrained1" + + // compute + cpu_speed = 2500 + max_cpu_number = 10 + min_cpu_number = 2 + + // memory + max_memory = 4096 + min_memory = 1024 + + // other + storage_tags = "foo" + host_tags = "test0101,test0202" + network_rate = 1024 + deployment_planner = "UserDispersingPlanner" + + //Feature flags + dynamic_scaling_enabled = false + is_volatile = false + limit_cpu_use = false + offer_ha = false + zone_ids = [] + +} +` + +const testAccServiceOfferingCustomConstrained1ZoneAll = ` +resource "cloudstack_service_offering_constrained" "constrained1" { + display_text = "constrained11" + name = "constrained1" + + // compute + cpu_speed = 2500 + max_cpu_number = 10 + min_cpu_number = 2 + + // memory + max_memory = 4096 + min_memory = 1024 + + // other + storage_tags = "foo1" + host_tags = "test0101,test0202" + network_rate = 1024 + deployment_planner = "UserDispersingPlanner" + + // Feature flags + dynamic_scaling_enabled = false + is_volatile = false + limit_cpu_use = false + offer_ha = false + zone_ids = [] +} +` + +const testAccServiceOfferingCustomConstrained2 = ` +resource "cloudstack_service_offering_constrained" "constrained2" { + display_text = "constrained2" + name = "constrained2" + + // compute + cpu_speed = 2500 + max_cpu_number = 10 + min_cpu_number = 2 + + // memory + max_memory = 4096 + min_memory = 1024 + + // other + host_tags = "test0101,test0202" + network_rate = 1024 + deployment_planner = "UserDispersingPlanner" + + // Feature flags + dynamic_scaling_enabled = true + is_volatile = true + limit_cpu_use = true + offer_ha = true +} +` +const testAccServiceOfferingCustomConstrained2_update = ` +resource "cloudstack_service_offering_constrained" "constrained2" { + display_text = "constrained2update" + name = "constrained2update" + + // compute + cpu_speed = 2500 + max_cpu_number = 10 + min_cpu_number = 2 + + // memory + max_memory = 4096 + min_memory = 1024 + + // other + host_tags = "test0101,test0202" + network_rate = 1024 + deployment_planner = "UserDispersingPlanner" + + // Feature flags + dynamic_scaling_enabled = true + is_volatile = true + limit_cpu_use = true + offer_ha = true +} +` + +const testAccServiceOfferingCustomConstrained_disk = ` +resource "cloudstack_service_offering_constrained" "constrained1" { + display_text = "constrained1" + name = "constrained1" + + // compute + cpu_speed = 2500 + max_cpu_number = 10 + min_cpu_number = 2 + + // memory + max_memory = 4096 + min_memory = 1024 + + // other + host_tags = "test0101,test0202" + network_rate = 1024 + deployment_planner = "UserDispersingPlanner" + + // Feature flags + dynamic_scaling_enabled = false + is_volatile = false + limit_cpu_use = false + offer_ha = false + + disk_offering = { + storage_type = "local" + provisioning_type = "thin" + cache_mode = "none" + root_disk_size = "5" + tags = "FOO" + disk_offering_strictness = false + } +} +` + +const testAccServiceOfferingCustomConstrained_disk_hypervisor = ` +resource "cloudstack_service_offering_constrained" "disk_hypervisor" { + display_text = "disk_hypervisor" + name = "disk_hypervisor" + + // compute + cpu_speed = 2500 + max_cpu_number = 10 + min_cpu_number = 2 + + // memory + max_memory = 4096 + min_memory = 1024 + + // other + host_tags = "test0101,test0202" + network_rate = 1024 + deployment_planner = "UserDispersingPlanner" + + // Feature flags + dynamic_scaling_enabled = false + is_volatile = false + limit_cpu_use = false + offer_ha = false + + disk_offering = { + storage_type = "local" + provisioning_type = "thin" + cache_mode = "none" + root_disk_size = "5" + tags = "FOO" + disk_offering_strictness = false + } + disk_hypervisor = { + bytes_read_rate = 1024 + bytes_read_rate_max = 1024 + bytes_read_rate_max_length = 1024 + bytes_write_rate = 1024 + bytes_write_rate_max = 1024 + bytes_write_rate_max_length = 1024 + } +} +` + +const testAccServiceOfferingCustomConstrained_disk_storage = ` +resource "cloudstack_service_offering_constrained" "disk_storage" { + display_text = "disk_storage" + name = "disk_storage" + + // compute + cpu_speed = 2500 + max_cpu_number = 10 + min_cpu_number = 2 + + // memory + max_memory = 4096 + min_memory = 1024 + + // other + host_tags = "test0101,test0202" + network_rate = 1024 + deployment_planner = "UserDispersingPlanner" + + // Feature flags + dynamic_scaling_enabled = false + is_volatile = false + limit_cpu_use = false + offer_ha = false + + disk_offering = { + storage_type = "local" + provisioning_type = "thin" + cache_mode = "none" + root_disk_size = "5" + tags = "FOO" + disk_offering_strictness = false + } + disk_hypervisor = { + bytes_read_rate = 1024 + bytes_read_rate_max = 1024 + bytes_read_rate_max_length = 1024 + bytes_write_rate = 1024 + bytes_write_rate_max = 1024 + bytes_write_rate_max_length = 1024 + } +} +` diff --git a/cloudstack/service_offering_fixed_resource.go b/cloudstack/service_offering_fixed_resource.go new file mode 100644 index 00000000..c512c329 --- /dev/null +++ b/cloudstack/service_offering_fixed_resource.go @@ -0,0 +1,242 @@ +package cloudstack + +import ( + "context" + "fmt" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int32planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +var ( + _ resource.Resource = &serviceOfferingFixedResource{} + _ resource.ResourceWithConfigure = &serviceOfferingFixedResource{} +) + +func NewserviceOfferingFixedResource() resource.Resource { + return &serviceOfferingFixedResource{} +} + +type serviceOfferingFixedResource struct { + client *cloudstack.CloudStackClient +} + +func (r *serviceOfferingFixedResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: serviceOfferingMergeCommonSchema(map[string]schema.Attribute{ + "cpu_number": schema.Int32Attribute{ + Required: true, + PlanModifiers: []planmodifier.Int32{ + int32planmodifier.RequiresReplace(), + }, + }, + "cpu_speed": schema.Int32Attribute{ + Required: true, + PlanModifiers: []planmodifier.Int32{ + int32planmodifier.RequiresReplace(), + }, + }, + "memory": schema.Int32Attribute{ + Required: true, + PlanModifiers: []planmodifier.Int32{ + int32planmodifier.RequiresReplace(), + }, + }, + }), + } +} + +func (r *serviceOfferingFixedResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // + var plan serviceOfferingFixedResourceModel + var planDiskQosHypervisor ServiceOfferingDiskQosHypervisor + var planDiskOffering ServiceOfferingDiskOffering + var planDiskQosStorage ServiceOfferingDiskQosStorage + + // + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if !plan.ServiceOfferingDiskQosHypervisor.IsNull() { + resp.Diagnostics.Append(plan.ServiceOfferingDiskQosHypervisor.As(ctx, &planDiskQosHypervisor, basetypes.ObjectAsOptions{})...) + } + if !plan.ServiceOfferingDiskOffering.IsNull() { + resp.Diagnostics.Append(plan.ServiceOfferingDiskOffering.As(ctx, &planDiskOffering, basetypes.ObjectAsOptions{})...) + } + if !plan.ServiceOfferingDiskQosStorage.IsNull() { + resp.Diagnostics.Append(plan.ServiceOfferingDiskQosStorage.As(ctx, &planDiskQosStorage, basetypes.ObjectAsOptions{})...) + } + if resp.Diagnostics.HasError() { + return + } + + // cloudstack params + params := r.client.ServiceOffering.NewCreateServiceOfferingParams(plan.DisplayText.ValueString(), plan.Name.ValueString()) + plan.commonCreateParams(params) + + // resource specific params + if !plan.CpuNumber.IsNull() { + params.SetCpunumber(int(plan.CpuNumber.ValueInt32())) + } + if !plan.CpuSpeed.IsNull() { + params.SetCpuspeed(int(plan.CpuSpeed.ValueInt32())) + } + if !plan.Memory.IsNull() { + params.SetMemory(int(plan.Memory.ValueInt32())) + } + + // create offering + cs, err := r.client.ServiceOffering.CreateServiceOffering(params) + if err != nil { + resp.Diagnostics.AddError( + "Error creating service offering", + "Could not create fixed offering, unexpected error: "+err.Error(), + ) + return + } + + // + plan.Id = types.StringValue(cs.Id) + + // + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) +} + +func (r *serviceOfferingFixedResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // + var state serviceOfferingFixedResourceModel + var stateDiskQosHypervisor ServiceOfferingDiskQosHypervisor + var stateDiskOffering ServiceOfferingDiskOffering + var stateDiskQosStorage ServiceOfferingDiskQosStorage + + // + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if !state.ServiceOfferingDiskQosHypervisor.IsNull() { + resp.Diagnostics.Append(state.ServiceOfferingDiskQosHypervisor.As(ctx, &stateDiskQosHypervisor, basetypes.ObjectAsOptions{})...) + } + if !state.ServiceOfferingDiskOffering.IsNull() { + resp.Diagnostics.Append(state.ServiceOfferingDiskOffering.As(ctx, &stateDiskOffering, basetypes.ObjectAsOptions{})...) + } + if !state.ServiceOfferingDiskQosStorage.IsNull() { + resp.Diagnostics.Append(state.ServiceOfferingDiskQosStorage.As(ctx, &stateDiskQosStorage, basetypes.ObjectAsOptions{})...) + } + if resp.Diagnostics.HasError() { + return + } + + // + cs, _, err := r.client.ServiceOffering.GetServiceOfferingByID(state.Id.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "Error reading service offering", + "Could not read fixed service offering, unexpected error: "+err.Error(), + ) + return + } + + // resource specific + if cs.Cpunumber > 0 { + state.CpuNumber = types.Int32Value(int32(cs.Cpunumber)) + } + if cs.Cpuspeed > 0 { + state.CpuSpeed = types.Int32Value(int32(cs.Cpuspeed)) + } + if cs.Memory > 0 { + state.Memory = types.Int32Value(int32(cs.Memory)) + } + + // + state.commonRead(ctx, cs) + stateDiskQosHypervisor.commonRead(ctx, cs) + stateDiskOffering.commonRead(ctx, cs) + stateDiskQosStorage.commonRead(ctx, cs) + if resp.Diagnostics.HasError() { + return + } + + // + resp.Diagnostics.Append(resp.State.Set(ctx, state)...) + +} + +// Update updates the resource and sets the updated Terraform state on success. +func (r *serviceOfferingFixedResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // + var state serviceOfferingFixedResourceModel + // + resp.Diagnostics.Append(req.Plan.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + // + params := r.client.ServiceOffering.NewUpdateServiceOfferingParams(state.Id.ValueString()) + state.commonUpdateParams(ctx, params) + + // + cs, err := r.client.ServiceOffering.UpdateServiceOffering(params) + if err != nil { + resp.Diagnostics.AddError( + "Error updating fixed service offering", + "Could not update fixed service offering, unexpected error: "+err.Error(), + ) + return + } + + // + state.commonUpdate(ctx, cs) + if resp.Diagnostics.HasError() { + return + } + resp.Diagnostics.Append(resp.State.Set(ctx, state)...) +} + +// Delete deletes the resource and removes the Terraform state on success. +func (r *serviceOfferingFixedResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // + var state serviceOfferingFixedResourceModel + + // + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Delete the service offering + _, err := r.client.ServiceOffering.DeleteServiceOffering(r.client.ServiceOffering.NewDeleteServiceOfferingParams(state.Id.ValueString())) + if err != nil { + resp.Diagnostics.AddError( + "Error deleting service offering", + "Could not delete fixed offering, unexpected error: "+err.Error(), + ) + return + } +} + +// Configure adds the provider configured client to the resource. +func (r *serviceOfferingFixedResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Add a nil check when handling ProviderData because Terraform + // sets that data after it calls the ConfigureProvider RPC. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*cloudstack.CloudStackClient) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *cloudstack.CloudStackClient, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + r.client = client +} + +// Metadata returns the resource type name. +func (r *serviceOfferingFixedResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_service_offering_fixed" +} diff --git a/cloudstack/service_offering_fixed_resource_test.go b/cloudstack/service_offering_fixed_resource_test.go new file mode 100644 index 00000000..56d23a5e --- /dev/null +++ b/cloudstack/service_offering_fixed_resource_test.go @@ -0,0 +1,239 @@ +// +// 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-plugin-testing/helper/resource" +) + +func TestAccServiceOfferingFixed(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccMuxProvider, + Steps: []resource.TestStep{ + { + Config: testAccServiceOfferingFixed1, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("cloudstack_service_offering_fixed.fixed1", "name", "fixed1"), + ), + }, + { + Config: testAccServiceOfferingFixed2, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("cloudstack_service_offering_fixed.fixed2", "name", "fixed2"), + ), + }, + { + Config: testAccServiceOfferingFixed2_update, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("cloudstack_service_offering_fixed.fixed2", "name", "fixed2update"), + ), + }, + { + Config: testAccServiceOfferingFixed_disk, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("cloudstack_service_offering_fixed.disk", "name", "disk"), + ), + }, + { + Config: testAccServiceOfferingFixed_disk_hypervisor, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("cloudstack_service_offering_fixed.disk_hypervisor", "name", "disk_hypervisor"), + ), + }, + { + Config: testAccServiceOfferingFixed_disk_storage, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("cloudstack_service_offering_fixed.disk_storage", "name", "disk_storage"), + ), + }, + }, + }) +} + +const testAccServiceOfferingFixed1 = ` +resource "cloudstack_service_offering_fixed" "fixed1" { + display_text = "fixed1" + name = "fixed1" + + // compute + cpu_number = 2 + cpu_speed = 2500 + memory = 2048 + + // other + host_tags = "test0101, test0202" + network_rate = 1024 + deployment_planner = "UserDispersingPlanner" + + dynamic_scaling_enabled = false + is_volatile = false + limit_cpu_use = false + offer_ha = false + +} +` + +const testAccServiceOfferingFixed2 = ` +resource "cloudstack_service_offering_fixed" "fixed2" { + display_text = "fixed2" + name = "fixed2" + + cpu_number = 2 + cpu_speed = 2500 + memory = 2048 + + host_tags = "test0101,test0202" + network_rate = 1024 + deployment_planner = "UserDispersingPlanner" + + dynamic_scaling_enabled = true + is_volatile = true + limit_cpu_use = true + offer_ha = true +} +` + +const testAccServiceOfferingFixed2_update = ` +resource "cloudstack_service_offering_fixed" "fixed2" { + display_text = "fixed2update" + name = "fixed2update" + + cpu_number = 2 + cpu_speed = 2500 + memory = 2048 + + host_tags = "test0101,test0202" + network_rate = 1024 + deployment_planner = "UserDispersingPlanner" + + dynamic_scaling_enabled = true + is_volatile = true + limit_cpu_use = true + offer_ha = true +} +` + +const testAccServiceOfferingFixed_disk = ` +resource "cloudstack_service_offering_fixed" "disk" { + display_text = "disk" + name = "disk" + + // compute + cpu_number = 2 + cpu_speed = 2500 + memory = 2048 + + // other + host_tags = "test0101, test0202" + network_rate = 1024 + deployment_planner = "UserDispersingPlanner" + + dynamic_scaling_enabled = false + is_volatile = false + limit_cpu_use = false + offer_ha = false + + disk_offering = { + storage_type = "local" + provisioning_type = "thin" + cache_mode = "none" + root_disk_size = "5" + tags = "FOO" + disk_offering_strictness = false + } +} +` + +const testAccServiceOfferingFixed_disk_hypervisor = ` +resource "cloudstack_service_offering_fixed" "disk_hypervisor" { + display_text = "disk_hypervisor" + name = "disk_hypervisor" + + // compute + cpu_number = 2 + cpu_speed = 2500 + memory = 2048 + + // other + host_tags = "test0101, test0202" + network_rate = 1024 + deployment_planner = "UserDispersingPlanner" + + dynamic_scaling_enabled = false + is_volatile = false + limit_cpu_use = false + offer_ha = false + + disk_offering = { + storage_type = "local" + provisioning_type = "thin" + cache_mode = "none" + root_disk_size = "5" + tags = "FOO" + disk_offering_strictness = false + } + disk_hypervisor = { + bytes_read_rate = 1024 + bytes_read_rate_max = 1024 + bytes_read_rate_max_length = 1024 + bytes_write_rate = 1024 + bytes_write_rate_max = 1024 + bytes_write_rate_max_length = 1024 + } +} +` + +const testAccServiceOfferingFixed_disk_storage = ` +resource "cloudstack_service_offering_fixed" "disk_storage" { + display_text = "disk_storage" + name = "disk_storage" + + // compute + cpu_number = 2 + cpu_speed = 2500 + memory = 2048 + + // other + host_tags = "test0101, test0202" + network_rate = 1024 + deployment_planner = "UserDispersingPlanner" + + dynamic_scaling_enabled = false + is_volatile = false + limit_cpu_use = false + offer_ha = false + + disk_offering = { + storage_type = "local" + provisioning_type = "thin" + cache_mode = "none" + root_disk_size = "5" + tags = "FOO" + disk_offering_strictness = false + } + disk_storage = { + min_iops = 100 + max_iops = 100 + } +} +` diff --git a/cloudstack/service_offering_models.go b/cloudstack/service_offering_models.go new file mode 100644 index 00000000..84f08d39 --- /dev/null +++ b/cloudstack/service_offering_models.go @@ -0,0 +1,70 @@ +package cloudstack + +import "github.com/hashicorp/terraform-plugin-framework/types" + +type serviceOfferingConstrainedResourceModel struct { + CpuSpeed types.Int32 `tfsdk:"cpu_speed"` + MaxCpuNumber types.Int32 `tfsdk:"max_cpu_number"` + MaxMemory types.Int32 `tfsdk:"max_memory"` + MinCpuNumber types.Int32 `tfsdk:"min_cpu_number"` + MinMemory types.Int32 `tfsdk:"min_memory"` + serviceOfferingCommonResourceModel +} + +// customized types.String `tfsdk:"Iscustomized"` + +type serviceOfferingUnconstrainedResourceModel struct { + serviceOfferingCommonResourceModel +} + +type serviceOfferingFixedResourceModel struct { + CpuNumber types.Int32 `tfsdk:"cpu_number"` + CpuSpeed types.Int32 `tfsdk:"cpu_speed"` + Memory types.Int32 `tfsdk:"memory"` + serviceOfferingCommonResourceModel +} + +type serviceOfferingCommonResourceModel struct { + DeploymentPlanner types.String `tfsdk:"deployment_planner"` + DiskOfferingId types.String `tfsdk:"disk_offering_id"` + DisplayText types.String `tfsdk:"display_text"` + DomainIds types.Set `tfsdk:"domain_ids"` + DynamicScalingEnabled types.Bool `tfsdk:"dynamic_scaling_enabled"` + HostTags types.String `tfsdk:"host_tags"` + Id types.String `tfsdk:"id"` + IsVolatile types.Bool `tfsdk:"is_volatile"` + LimitCpuUse types.Bool `tfsdk:"limit_cpu_use"` + Name types.String `tfsdk:"name"` + NetworkRate types.Int32 `tfsdk:"network_rate"` + OfferHa types.Bool `tfsdk:"offer_ha"` + StorageTags types.String `tfsdk:"storage_tags"` + ZoneIds types.Set `tfsdk:"zone_ids"` + // + ServiceOfferingDiskQosHypervisor types.Object `tfsdk:"disk_hypervisor"` + ServiceOfferingDiskOffering types.Object `tfsdk:"disk_offering"` + ServiceOfferingDiskQosStorage types.Object `tfsdk:"disk_storage"` +} + +type ServiceOfferingDiskQosHypervisor struct { + DiskBytesReadRate types.Int64 `tfsdk:"bytes_read_rate"` + DiskBytesReadRateMax types.Int64 `tfsdk:"bytes_read_rate_max"` + DiskBytesReadRateMaxLength types.Int64 `tfsdk:"bytes_read_rate_max_length"` + DiskBytesWriteRate types.Int64 `tfsdk:"bytes_write_rate"` + DiskBytesWriteRateMax types.Int64 `tfsdk:"bytes_write_rate_max"` + DiskBytesWriteRateMaxLength types.Int64 `tfsdk:"bytes_write_rate_max_length"` +} + +type ServiceOfferingDiskOffering struct { + CacheMode types.String `tfsdk:"cache_mode"` + DiskOfferingStrictness types.Bool `tfsdk:"disk_offering_strictness"` + ProvisionType types.String `tfsdk:"provisioning_type"` + RootDiskSize types.Int64 `tfsdk:"root_disk_size"` + StorageType types.String `tfsdk:"storage_type"` +} + +type ServiceOfferingDiskQosStorage struct { + CustomizedIops types.Bool `tfsdk:"customized_iops"` + HypervisorSnapshotReserve types.Int32 `tfsdk:"hypervisor_snapshot_reserve"` + MaxIops types.Int64 `tfsdk:"max_iops"` + MinIops types.Int64 `tfsdk:"min_iops"` +} diff --git a/cloudstack/service_offering_schema.go b/cloudstack/service_offering_schema.go new file mode 100644 index 00000000..3aeb82be --- /dev/null +++ b/cloudstack/service_offering_schema.go @@ -0,0 +1,196 @@ +package cloudstack + +import ( + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int32planmodifier" + "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/types" +) + +func serviceOfferingMergeCommonSchema(s1 map[string]schema.Attribute) map[string]schema.Attribute { + common := map[string]schema.Attribute{ + "deployment_planner": schema.StringAttribute{ + Optional: true, + }, + "disk_offering_id": schema.StringAttribute{ + Optional: true, + }, + "display_text": schema.StringAttribute{ + Required: true, + }, + "domain_ids": schema.SetAttribute{ + Optional: true, + ElementType: types.StringType, + }, + "dynamic_scaling_enabled": schema.BoolAttribute{ + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.RequiresReplace(), + }, + Default: booldefault.StaticBool(false), + }, + "host_tags": schema.StringAttribute{ + Optional: true, + }, + "id": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "is_volatile": schema.BoolAttribute{ + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.RequiresReplace(), + }, + Default: booldefault.StaticBool(false), + }, + "limit_cpu_use": schema.BoolAttribute{ + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.RequiresReplace(), + }, + Default: booldefault.StaticBool(false), + }, + "name": schema.StringAttribute{ + Required: true, + }, + "network_rate": schema.Int32Attribute{ + Optional: true, + }, + "offer_ha": schema.BoolAttribute{ + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.RequiresReplace(), + }, + Default: booldefault.StaticBool(false), + }, + "storage_tags": schema.StringAttribute{ + Optional: true, + }, + "zone_ids": schema.SetAttribute{ + Optional: true, + ElementType: types.StringType, + }, + "disk_hypervisor": schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "bytes_read_rate": schema.Int64Attribute{ + Required: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.RequiresReplace(), + }, + }, + "bytes_read_rate_max": schema.Int64Attribute{ + Required: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.RequiresReplace(), + }, + }, + "bytes_read_rate_max_length": schema.Int64Attribute{ + Required: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.RequiresReplace(), + }, + }, + "bytes_write_rate": schema.Int64Attribute{ + Required: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.RequiresReplace(), + }, + }, + "bytes_write_rate_max": schema.Int64Attribute{ + Required: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.RequiresReplace(), + }, + }, + "bytes_write_rate_max_length": schema.Int64Attribute{ + Required: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.RequiresReplace(), + }, + }, + }, + }, + "disk_offering": schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "cache_mode": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "disk_offering_strictness": schema.BoolAttribute{ + Required: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.RequiresReplace(), + }, + }, + "provisioning_type": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "root_disk_size": schema.Int64Attribute{ + Required: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.RequiresReplace(), + }, + }, + "storage_type": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + }, + "disk_storage": schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "customized_iops": schema.BoolAttribute{ + Optional: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.RequiresReplace(), + }, + }, + "hypervisor_snapshot_reserve": schema.Int32Attribute{ + Optional: true, + PlanModifiers: []planmodifier.Int32{ + int32planmodifier.RequiresReplace(), + }, + }, + "max_iops": schema.Int64Attribute{ + Optional: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.RequiresReplace(), + }, + }, + "min_iops": schema.Int64Attribute{ + Optional: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.RequiresReplace(), + }, + }, + }, + }, + } + + for key, value := range s1 { + common[key] = value + } + + return common + +} diff --git a/cloudstack/service_offering_unconstrained_resource.go b/cloudstack/service_offering_unconstrained_resource.go new file mode 100644 index 00000000..0087b0f6 --- /dev/null +++ b/cloudstack/service_offering_unconstrained_resource.go @@ -0,0 +1,202 @@ +package cloudstack + +import ( + "context" + "fmt" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +var ( + _ resource.Resource = &serviceOfferingUnconstrainedResource{} + _ resource.ResourceWithConfigure = &serviceOfferingUnconstrainedResource{} +) + +func NewserviceOfferingUnconstrainedResource() resource.Resource { + return &serviceOfferingUnconstrainedResource{} +} + +type serviceOfferingUnconstrainedResource struct { + client *cloudstack.CloudStackClient +} + +// Schema defines the schema for the resource. +func (r *serviceOfferingUnconstrainedResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: serviceOfferingMergeCommonSchema(map[string]schema.Attribute{}), + } +} + +func (r *serviceOfferingUnconstrainedResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // + var plan serviceOfferingUnconstrainedResourceModel + var planDiskQosHypervisor ServiceOfferingDiskQosHypervisor + var planDiskOffering ServiceOfferingDiskOffering + var planDiskQosStorage ServiceOfferingDiskQosStorage + + // + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if !plan.ServiceOfferingDiskQosHypervisor.IsNull() { + resp.Diagnostics.Append(plan.ServiceOfferingDiskQosHypervisor.As(ctx, &planDiskQosHypervisor, basetypes.ObjectAsOptions{})...) + } + if !plan.ServiceOfferingDiskOffering.IsNull() { + resp.Diagnostics.Append(plan.ServiceOfferingDiskOffering.As(ctx, &planDiskOffering, basetypes.ObjectAsOptions{})...) + } + if !plan.ServiceOfferingDiskQosStorage.IsNull() { + resp.Diagnostics.Append(plan.ServiceOfferingDiskQosStorage.As(ctx, &planDiskQosStorage, basetypes.ObjectAsOptions{})...) + } + if resp.Diagnostics.HasError() { + return + } + + // cloudstack params + params := r.client.ServiceOffering.NewCreateServiceOfferingParams(plan.DisplayText.ValueString(), plan.Name.ValueString()) + plan.commonCreateParams(params) + planDiskQosHypervisor.commonCreateParams(params) + planDiskOffering.commonCreateParams(params) + planDiskQosStorage.commonCreateParams(params) + + // create offering + cs, err := r.client.ServiceOffering.CreateServiceOffering(params) + if err != nil { + resp.Diagnostics.AddError( + "Error creating service offering", + "Could not create unconstrained offering, unexpected error: "+err.Error(), + ) + return + } + + // + plan.Id = types.StringValue(cs.Id) + + // + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) + +} + +func (r *serviceOfferingUnconstrainedResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // + var state serviceOfferingUnconstrainedResourceModel + var stateDiskQosHypervisor ServiceOfferingDiskQosHypervisor + var stateDiskOffering ServiceOfferingDiskOffering + var stateDiskQosStorage ServiceOfferingDiskQosStorage + + // + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if !state.ServiceOfferingDiskQosHypervisor.IsNull() { + resp.Diagnostics.Append(state.ServiceOfferingDiskQosHypervisor.As(ctx, &stateDiskQosHypervisor, basetypes.ObjectAsOptions{})...) + } + if !state.ServiceOfferingDiskOffering.IsNull() { + resp.Diagnostics.Append(state.ServiceOfferingDiskOffering.As(ctx, &stateDiskOffering, basetypes.ObjectAsOptions{})...) + } + if !state.ServiceOfferingDiskQosStorage.IsNull() { + resp.Diagnostics.Append(state.ServiceOfferingDiskQosStorage.As(ctx, &stateDiskQosStorage, basetypes.ObjectAsOptions{})...) + } + if resp.Diagnostics.HasError() { + return + } + + // + cs, _, err := r.client.ServiceOffering.GetServiceOfferingByID(state.Id.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "Error creating service offering", + "Could not read unconstrained service offering, unexpected error: "+err.Error(), + ) + return + } + + // + state.commonRead(ctx, cs) + stateDiskQosHypervisor.commonRead(ctx, cs) + stateDiskOffering.commonRead(ctx, cs) + stateDiskQosStorage.commonRead(ctx, cs) + if resp.Diagnostics.HasError() { + return + } + + // + resp.Diagnostics.Append(resp.State.Set(ctx, state)...) + +} + +func (r *serviceOfferingUnconstrainedResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // + var state serviceOfferingUnconstrainedResourceModel + + // + resp.Diagnostics.Append(req.Plan.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + // + params := r.client.ServiceOffering.NewUpdateServiceOfferingParams(state.Id.ValueString()) + state.commonUpdateParams(ctx, params) + + // + cs, err := r.client.ServiceOffering.UpdateServiceOffering(params) + if err != nil { + resp.Diagnostics.AddError( + "Error updating service offering", + "Could not update unconstrained service offering, unexpected error: "+err.Error(), + ) + return + } + + // + state.commonUpdate(ctx, cs) + if resp.Diagnostics.HasError() { + return + } + resp.Diagnostics.Append(resp.State.Set(ctx, state)...) + +} + +func (r *serviceOfferingUnconstrainedResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // + var state serviceOfferingUnconstrainedResourceModel + + // + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Delete the service offering + _, err := r.client.ServiceOffering.DeleteServiceOffering(r.client.ServiceOffering.NewDeleteServiceOfferingParams(state.Id.ValueString())) + if err != nil { + resp.Diagnostics.AddError( + "Error deleting service offering", + "Could not delete unconstrained offering, unexpected error: "+err.Error(), + ) + return + } +} + +func (r *serviceOfferingUnconstrainedResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*cloudstack.CloudStackClient) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *cloudstack.CloudStackClient, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + r.client = client +} + +func (r *serviceOfferingUnconstrainedResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_service_offering_unconstrained" +} diff --git a/cloudstack/service_offering_unconstrained_resource_test.go b/cloudstack/service_offering_unconstrained_resource_test.go new file mode 100644 index 00000000..82bafdda --- /dev/null +++ b/cloudstack/service_offering_unconstrained_resource_test.go @@ -0,0 +1,207 @@ +// // +// // 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-plugin-testing/helper/resource" +) + +func TestAccServiceOfferingUnconstrained(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccMuxProvider, + Steps: []resource.TestStep{ + { + Config: testAccServiceOfferingUnconstrained1, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("cloudstack_service_offering_unconstrained.unconstrained1", "name", "unconstrained1"), + ), + }, + { + Config: testAccServiceOfferingUnconstrained2, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("cloudstack_service_offering_unconstrained.unconstrained2", "name", "unconstrained2"), + ), + }, + { + Config: testAccServiceOfferingUnconstrained2_update, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("cloudstack_service_offering_unconstrained.unconstrained2", "name", "unconstrained2update"), + ), + }, + { + Config: testAccServiceOfferingUnconstrained_disk, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("cloudstack_service_offering_unconstrained.disk", "name", "disk"), + ), + }, + { + Config: testAccServiceOfferingUnconstrained_disk_hypervisor, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("cloudstack_service_offering_unconstrained.disk_hypervisor", "name", "disk_hypervisor"), + ), + }, + { + Config: testAccServiceOfferingUnconstrained_disk_storage, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("cloudstack_service_offering_unconstrained.disk_storage", "name", "disk_storage"), + ), + }, + }, + }) +} + +const testAccServiceOfferingUnconstrained1 = ` +resource "cloudstack_service_offering_unconstrained" "unconstrained1" { + display_text = "unconstrained1" + name = "unconstrained1" + + host_tags = "test0101,test0202" + network_rate = 1024 + deployment_planner = "UserDispersingPlanner" + + dynamic_scaling_enabled = false + is_volatile = false + limit_cpu_use = false + offer_ha = false +} +` + +const testAccServiceOfferingUnconstrained2 = ` +resource "cloudstack_service_offering_unconstrained" "unconstrained2" { + display_text = "unconstrained2" + name = "unconstrained2" + + host_tags = "test0101,test0202" + network_rate = 1024 + deployment_planner = "UserDispersingPlanner" + + dynamic_scaling_enabled = true + is_volatile = true + limit_cpu_use = true + offer_ha = true +} +` + +const testAccServiceOfferingUnconstrained2_update = ` +resource "cloudstack_service_offering_unconstrained" "unconstrained2" { + display_text = "unconstrained2update" + name = "unconstrained2update" + + host_tags = "test0101,test0202" + network_rate = 1024 + deployment_planner = "UserDispersingPlanner" + + dynamic_scaling_enabled = true + is_volatile = true + limit_cpu_use = true + offer_ha = true +} +` + +const testAccServiceOfferingUnconstrained_disk = ` +resource "cloudstack_service_offering_unconstrained" "disk" { + display_text = "disk" + name = "disk" + + host_tags = "test0101,test0202" + network_rate = 1024 + deployment_planner = "UserDispersingPlanner" + + dynamic_scaling_enabled = true + is_volatile = true + limit_cpu_use = true + offer_ha = true + + disk_offering = { + storage_type = "shared" + provisioning_type = "thin" + cache_mode = "none" + root_disk_size = "5" + tags = "FOO" + disk_offering_strictness = false + } +} +` + +const testAccServiceOfferingUnconstrained_disk_hypervisor = ` +resource "cloudstack_service_offering_unconstrained" "disk_hypervisor" { + display_text = "disk_hypervisor" + name = "disk_hypervisor" + + host_tags = "test0101,test0202" + network_rate = 1024 + deployment_planner = "UserDispersingPlanner" + + dynamic_scaling_enabled = true + is_volatile = true + limit_cpu_use = true + offer_ha = true + + disk_offering = { + storage_type = "shared" + provisioning_type = "thin" + cache_mode = "none" + root_disk_size = "5" + tags = "FOO" + disk_offering_strictness = false + } + disk_hypervisor = { + bytes_read_rate = 1024 + bytes_read_rate_max = 1024 + bytes_read_rate_max_length = 1024 + bytes_write_rate = 1024 + bytes_write_rate_max = 1024 + bytes_write_rate_max_length = 1024 + } + +} +` + +const testAccServiceOfferingUnconstrained_disk_storage = ` +resource "cloudstack_service_offering_unconstrained" "disk_storage" { + display_text = "disk_storage" + name = "disk_storage" + + host_tags = "test0101,test0202" + network_rate = 1024 + deployment_planner = "UserDispersingPlanner" + + dynamic_scaling_enabled = true + is_volatile = true + limit_cpu_use = true + offer_ha = true + + disk_offering = { + storage_type = "shared" + provisioning_type = "thin" + cache_mode = "none" + root_disk_size = "5" + tags = "FOO" + disk_offering_strictness = false + } + disk_storage = { + min_iops = 100 + max_iops = 100 + } +} +` diff --git a/cloudstack/service_offering_util.go b/cloudstack/service_offering_util.go new file mode 100644 index 00000000..9dd51e78 --- /dev/null +++ b/cloudstack/service_offering_util.go @@ -0,0 +1,267 @@ +package cloudstack + +import ( + "context" + "strings" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// ------------------------------------------------------------------------------------------------------------------------------ +// Common update methods +// - +func (state *serviceOfferingCommonResourceModel) commonUpdate(ctx context.Context, cs *cloudstack.UpdateServiceOfferingResponse) { + if cs.Displaytext != "" { + state.DisplayText = types.StringValue(cs.Displaytext) + } + if cs.Domainid != "" { + state.DomainIds, _ = types.SetValueFrom(ctx, types.StringType, strings.Split(cs.Domainid, ",")) + } + if cs.Hosttags != "" { + state.HostTags = types.StringValue(cs.Hosttags) + } + if cs.Name != "" { + state.Name = types.StringValue(cs.Name) + } + if cs.Storagetags != "" { + state.StorageTags = types.StringValue(cs.Storagetags) + } + if cs.Zoneid != "" { + state.ZoneIds, _ = types.SetValueFrom(ctx, types.StringType, strings.Split(cs.Zoneid, ",")) + } +} + +func (plan *serviceOfferingCommonResourceModel) commonUpdateParams(ctx context.Context, p *cloudstack.UpdateServiceOfferingParams) *cloudstack.UpdateServiceOfferingParams { + if !plan.DisplayText.IsNull() { + p.SetDisplaytext(plan.DisplayText.ValueString()) + } + if !plan.DomainIds.IsNull() { + p.SetDomainid(plan.DomainIds.String()) + } + if !plan.HostTags.IsNull() { + p.SetHosttags(plan.HostTags.ValueString()) + } + if !plan.Name.IsNull() { + p.SetName(plan.Name.ValueString()) + } + if !plan.StorageTags.IsNull() { + p.SetStoragetags(plan.StorageTags.ValueString()) + } + if !plan.ZoneIds.IsNull() && len(plan.ZoneIds.Elements()) > 0 { + p.SetZoneid(plan.ZoneIds.String()) + } + + return p + +} + +// ------------------------------------------------------------------------------------------------------------------------------ +// common Read methods +// - +func (state *serviceOfferingCommonResourceModel) commonRead(ctx context.Context, cs *cloudstack.ServiceOffering) { + state.Id = types.StringValue(cs.Id) + if cs.Deploymentplanner != "" { + state.DeploymentPlanner = types.StringValue(cs.Deploymentplanner) + } + if cs.Diskofferingid != "" { + state.DiskOfferingId = types.StringValue(cs.Diskofferingid) + } + if cs.Displaytext != "" { + state.DisplayText = types.StringValue(cs.Displaytext) + } + if cs.Domainid != "" { + state.DomainIds, _ = types.SetValueFrom(ctx, types.StringType, strings.Split(cs.Domainid, ",")) + } + if cs.Hosttags != "" { + state.HostTags = types.StringValue(cs.Hosttags) + } + if cs.Name != "" { + state.Name = types.StringValue(cs.Name) + } + if cs.Networkrate > 0 { + state.NetworkRate = types.Int32Value(int32(cs.Networkrate)) + } + if cs.Storagetags != "" { + state.StorageTags = types.StringValue(cs.Storagetags) + } + if cs.Zoneid != "" { + state.ZoneIds, _ = types.SetValueFrom(ctx, types.StringType, strings.Split(cs.Zoneid, ",")) + } + + state.DynamicScalingEnabled = types.BoolValue(cs.Dynamicscalingenabled) + state.IsVolatile = types.BoolValue(cs.Isvolatile) + state.LimitCpuUse = types.BoolValue(cs.Limitcpuuse) + state.OfferHa = types.BoolValue(cs.Offerha) + +} + +func (state *ServiceOfferingDiskQosHypervisor) commonRead(ctx context.Context, cs *cloudstack.ServiceOffering) { + if cs.DiskBytesReadRate > 0 { + state.DiskBytesReadRate = types.Int64Value(cs.DiskBytesReadRate) + } + if cs.DiskBytesReadRateMax > 0 { + state.DiskBytesReadRateMax = types.Int64Value(cs.DiskBytesReadRateMax) + } + if cs.DiskBytesReadRateMaxLength > 0 { + state.DiskBytesReadRateMaxLength = types.Int64Value(cs.DiskBytesReadRateMaxLength) + } + if cs.DiskBytesWriteRate > 0 { + state.DiskBytesWriteRate = types.Int64Value(cs.DiskBytesWriteRate) + } + if cs.DiskBytesWriteRateMax > 0 { + state.DiskBytesWriteRateMax = types.Int64Value(cs.DiskBytesWriteRateMax) + } + if cs.DiskBytesWriteRateMaxLength > 0 { + state.DiskBytesWriteRateMaxLength = types.Int64Value(cs.DiskBytesWriteRateMaxLength) + } + +} + +func (state *ServiceOfferingDiskOffering) commonRead(ctx context.Context, cs *cloudstack.ServiceOffering) { + + if cs.CacheMode != "" { + state.CacheMode = types.StringValue(cs.CacheMode) + } + if cs.Diskofferingstrictness { + state.DiskOfferingStrictness = types.BoolValue(cs.Diskofferingstrictness) + } + if cs.Provisioningtype != "" { + state.ProvisionType = types.StringValue(cs.Provisioningtype) + } + if cs.Rootdisksize > 0 { + state.RootDiskSize = types.Int64Value(cs.Rootdisksize) + } + if cs.Storagetype != "" { + state.StorageType = types.StringValue(cs.Storagetype) + } +} + +func (state *ServiceOfferingDiskQosStorage) commonRead(ctx context.Context, cs *cloudstack.ServiceOffering) { + if cs.Iscustomizediops { + state.CustomizedIops = types.BoolValue(cs.Iscustomizediops) + } + if cs.Hypervisorsnapshotreserve > 0 { + state.HypervisorSnapshotReserve = types.Int32Value(int32(cs.Hypervisorsnapshotreserve)) + } + if cs.Maxiops > 0 { + state.MaxIops = types.Int64Value(cs.Maxiops) + } + if cs.Miniops > 0 { + state.MinIops = types.Int64Value(cs.Miniops) + } + +} + +// ------------------------------------------------------------------------------------------------------------------------------ +// common Create methods +// - +func (plan *serviceOfferingCommonResourceModel) commonCreateParams(p *cloudstack.CreateServiceOfferingParams) *cloudstack.CreateServiceOfferingParams { + if !plan.DeploymentPlanner.IsNull() && !plan.DeploymentPlanner.IsUnknown() { + p.SetDeploymentplanner(plan.DeploymentPlanner.ValueString()) + } else { + plan.DeploymentPlanner = types.StringNull() + } + if !plan.DiskOfferingId.IsNull() { + p.SetDiskofferingid(plan.DiskOfferingId.ValueString()) + } + if !plan.DomainIds.IsNull() { + domainids := make([]string, len(plan.DomainIds.Elements())) + for i, v := range plan.DomainIds.Elements() { + domainids[i] = v.String() + } + p.SetDomainid(domainids) + } + if !plan.DynamicScalingEnabled.IsNull() { + p.SetDynamicscalingenabled(plan.DynamicScalingEnabled.ValueBool()) + } + if !plan.HostTags.IsNull() { + p.SetHosttags(plan.HostTags.ValueString()) + } + if !plan.IsVolatile.IsNull() { + p.SetIsvolatile(plan.IsVolatile.ValueBool()) + } + if !plan.LimitCpuUse.IsNull() { + p.SetLimitcpuuse(plan.LimitCpuUse.ValueBool()) + } + if !plan.NetworkRate.IsNull() { + p.SetNetworkrate(int(plan.NetworkRate.ValueInt32())) + } + if !plan.OfferHa.IsNull() { + p.SetOfferha(plan.OfferHa.ValueBool()) + } + if !plan.StorageTags.IsNull() { + p.SetTags(plan.StorageTags.ValueString()) + } + if !plan.ZoneIds.IsNull() { + zids := make([]string, len(plan.ZoneIds.Elements())) + for i, v := range plan.ZoneIds.Elements() { + zids[i] = v.String() + } + p.SetZoneid(zids) + } + + return p + +} +func (plan *ServiceOfferingDiskQosHypervisor) commonCreateParams(p *cloudstack.CreateServiceOfferingParams) *cloudstack.CreateServiceOfferingParams { + if !plan.DiskBytesReadRate.IsNull() { + p.SetBytesreadrate(plan.DiskBytesReadRate.ValueInt64()) + } + if !plan.DiskBytesReadRateMax.IsNull() { + p.SetBytesreadratemax(plan.DiskBytesReadRateMax.ValueInt64()) + } + if !plan.DiskBytesReadRateMaxLength.IsNull() { + p.SetBytesreadratemaxlength(plan.DiskBytesReadRateMaxLength.ValueInt64()) + } + if !plan.DiskBytesWriteRate.IsNull() { + p.SetByteswriterate(plan.DiskBytesWriteRate.ValueInt64()) + } + if !plan.DiskBytesWriteRateMax.IsNull() { + p.SetByteswriteratemax(plan.DiskBytesWriteRateMax.ValueInt64()) + } + if !plan.DiskBytesWriteRateMaxLength.IsNull() { + p.SetByteswriteratemaxlength(plan.DiskBytesWriteRateMaxLength.ValueInt64()) + } + + return p +} + +func (plan *ServiceOfferingDiskOffering) commonCreateParams(p *cloudstack.CreateServiceOfferingParams) *cloudstack.CreateServiceOfferingParams { + + if !plan.CacheMode.IsNull() { + p.SetCachemode(plan.CacheMode.ValueString()) + } + if !plan.DiskOfferingStrictness.IsNull() { + p.SetDiskofferingstrictness(plan.DiskOfferingStrictness.ValueBool()) + } + if !plan.ProvisionType.IsNull() { + p.SetProvisioningtype(plan.ProvisionType.ValueString()) + } + if !plan.RootDiskSize.IsNull() { + p.SetRootdisksize(plan.RootDiskSize.ValueInt64()) + } + if !plan.StorageType.IsNull() { + p.SetStoragetype(plan.StorageType.ValueString()) + } + + return p + +} + +func (plan *ServiceOfferingDiskQosStorage) commonCreateParams(p *cloudstack.CreateServiceOfferingParams) *cloudstack.CreateServiceOfferingParams { + if !plan.CustomizedIops.IsNull() { + p.SetCustomizediops(plan.CustomizedIops.ValueBool()) + } + if !plan.HypervisorSnapshotReserve.IsNull() { + p.SetHypervisorsnapshotreserve(int(plan.HypervisorSnapshotReserve.ValueInt32())) + } + if !plan.MaxIops.IsNull() { + p.SetMaxiops(int64(plan.MaxIops.ValueInt64())) + } + if !plan.MinIops.IsNull() { + p.SetMiniops((plan.MinIops.ValueInt64())) + } + + return p +} diff --git a/go.mod b/go.mod index 8952c350..7b36c049 100644 --- a/go.mod +++ b/go.mod @@ -4,10 +4,10 @@ require ( github.com/apache/cloudstack-go/v2 v2.16.1 github.com/go-ini/ini v1.67.0 github.com/hashicorp/go-multierror v1.1.1 - github.com/hashicorp/terraform-plugin-framework v1.7.0 + github.com/hashicorp/terraform-plugin-framework v1.12.0 github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 - github.com/hashicorp/terraform-plugin-go v0.22.1 - github.com/hashicorp/terraform-plugin-mux v0.15.0 + github.com/hashicorp/terraform-plugin-go v0.24.0 + github.com/hashicorp/terraform-plugin-mux v0.16.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0 github.com/hashicorp/terraform-plugin-testing v1.7.0 ) @@ -19,14 +19,14 @@ require ( github.com/cloudflare/circl v1.3.7 // indirect github.com/fatih/color v1.16.0 // indirect github.com/golang/mock v1.6.0 // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-checkpoint v0.5.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect github.com/hashicorp/go-hclog v1.6.2 // indirect - github.com/hashicorp/go-plugin v1.6.0 // indirect + github.com/hashicorp/go-plugin v1.6.1 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/hc-install v0.6.3 // indirect @@ -53,19 +53,20 @@ require ( github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/zclconf/go-cty v1.14.3 // indirect - golang.org/x/crypto v0.21.0 // indirect - golang.org/x/mod v0.15.0 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.13.0 // indirect + golang.org/x/crypto v0.24.0 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect - google.golang.org/grpc v1.62.1 // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect + google.golang.org/grpc v1.66.2 // indirect + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) -go 1.21 +go 1.22.0 -toolchain go1.21.6 +toolchain go1.22.4 diff --git a/go.sum b/go.sum index 51980c36..af0f5e12 100644 --- a/go.sum +++ b/go.sum @@ -43,8 +43,8 @@ github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+Licev github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -62,8 +62,8 @@ github.com/hashicorp/go-hclog v1.6.2 h1:NOtoftovWkDheyUM/8JW3QMiXyxJK3uHRK7wV04n github.com/hashicorp/go-hclog v1.6.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-plugin v1.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDmTk8A= -github.com/hashicorp/go-plugin v1.6.0/go.mod h1:lBS5MtSSBZk0SHc66KACcjjlU6WzEVP/8pwz68aMkCI= +github.com/hashicorp/go-plugin v1.6.1 h1:P7MR2UP6gNKGPp+y7EZw2kOiq4IR9WiqLvp0XOsVdwI= +github.com/hashicorp/go-plugin v1.6.1/go.mod h1:XPHFku2tFo3o3QKFgSYo+cghcUhw1NA1hZyMK0PWAw0= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -79,16 +79,16 @@ github.com/hashicorp/terraform-exec v0.20.0 h1:DIZnPsqzPGuUnq6cH8jWcPunBfY+C+M8J github.com/hashicorp/terraform-exec v0.20.0/go.mod h1:ckKGkJWbsNqFKV1itgMnE0hY9IYf1HoiekpuN0eWoDw= github.com/hashicorp/terraform-json v0.21.0 h1:9NQxbLNqPbEMze+S6+YluEdXgJmhQykRyRNd+zTI05U= github.com/hashicorp/terraform-json v0.21.0/go.mod h1:qdeBs11ovMzo5puhrRibdD6d2Dq6TyE/28JiU4tIQxk= -github.com/hashicorp/terraform-plugin-framework v1.7.0 h1:wOULbVmfONnJo9iq7/q+iBOBJul5vRovaYJIu2cY/Pw= -github.com/hashicorp/terraform-plugin-framework v1.7.0/go.mod h1:jY9Id+3KbZ17OMpulgnWLSfwxNVYSoYBQFTgsx044CI= +github.com/hashicorp/terraform-plugin-framework v1.12.0 h1:7HKaueHPaikX5/7cbC1r9d1m12iYHY+FlNZEGxQ42CQ= +github.com/hashicorp/terraform-plugin-framework v1.12.0/go.mod h1:N/IOQ2uYjW60Jp39Cp3mw7I/OpC/GfZ0385R0YibmkE= github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 h1:HOjBuMbOEzl7snOdOoUfE2Jgeto6JOjLVQ39Ls2nksc= github.com/hashicorp/terraform-plugin-framework-validators v0.12.0/go.mod h1:jfHGE/gzjxYz6XoUwi/aYiiKrJDeutQNUtGQXkaHklg= -github.com/hashicorp/terraform-plugin-go v0.22.1 h1:iTS7WHNVrn7uhe3cojtvWWn83cm2Z6ryIUDTRO0EV7w= -github.com/hashicorp/terraform-plugin-go v0.22.1/go.mod h1:qrjnqRghvQ6KnDbB12XeZ4FluclYwptntoWCr9QaXTI= +github.com/hashicorp/terraform-plugin-go v0.24.0 h1:2WpHhginCdVhFIrWHxDEg6RBn3YaWzR2o6qUeIEat2U= +github.com/hashicorp/terraform-plugin-go v0.24.0/go.mod h1:tUQ53lAsOyYSckFGEefGC5C8BAaO0ENqzFd3bQeuYQg= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= -github.com/hashicorp/terraform-plugin-mux v0.15.0 h1:+/+lDx0WUsIOpkAmdwBIoFU8UP9o2eZASoOnLsWbKME= -github.com/hashicorp/terraform-plugin-mux v0.15.0/go.mod h1:9ezplb1Dyq394zQ+ldB0nvy/qbNAz3mMoHHseMTMaKo= +github.com/hashicorp/terraform-plugin-mux v0.16.0 h1:RCzXHGDYwUwwqfYYWJKBFaS3fQsWn/ZECEiW7p2023I= +github.com/hashicorp/terraform-plugin-mux v0.16.0/go.mod h1:PF79mAsPc8CpusXPfEVa4X8PtkB+ngWoiUClMrNZlYo= github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0 h1:qHprzXy/As0rxedphECBEQAh3R4yp6pKksKHcqZx5G8= github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0/go.mod h1:H+8tjs9TjV2w57QFVSMBQacf8k/E1XwLXGCARgViC6A= github.com/hashicorp/terraform-plugin-testing v1.7.0 h1:I6aeCyZ30z4NiI3tzyDoO6fS7YxP5xSL1ceOon3gTe8= @@ -168,25 +168,25 @@ github.com/zclconf/go-cty v1.14.3/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgr golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= -golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -202,8 +202,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -211,14 +211,14 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -226,14 +226,14 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= -google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= -google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo= +google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=