diff --git a/internal/interfaces/svm.go b/internal/interfaces/svm.go index 3068d9a2..7674fc41 100644 --- a/internal/interfaces/svm.go +++ b/internal/interfaces/svm.go @@ -81,7 +81,6 @@ func CreateSvm(errorHandler *utils.ErrorHandler, r restclient.RestClient, data S var body map[string]interface{} if err := mapstructure.Decode(data, &body); err != nil { return nil, errorHandler.MakeAndReportError("error encoding vserver body", fmt.Sprintf("error on encoding svm/svms body: %s, body: %#v", err, data)) - } query := r.NewQuery() query.Add("return_records", "true") @@ -111,6 +110,25 @@ func DeleteSvm(errorHandler *utils.ErrorHandler, r restclient.RestClient, uuid s return nil } +// UpdateSvm to update a vserver +func UpdateSvm(errorHandler *utils.ErrorHandler, r restclient.RestClient, data SvmResourceModel, uuid string, rename bool) error { + var body map[string]interface{} + if err := mapstructure.Decode(data, &body); err != nil { + return errorHandler.MakeAndReportError("error encoding vserver body", fmt.Sprintf("error on encoding svm/svms body: %s, body: %#v", err, data)) + } + // Name is only passed to patch if it is a rename + if !rename { + delete(body, "name") + } + query := r.NewQuery() + query.Add("return_records", "true") + statusCode, _, err := r.CallUpdateMethod("svm/svms/"+uuid, query, body) + if err != nil { + return errorHandler.MakeAndReportError("error updating vserver", fmt.Sprintf("error on PATCH svm/svms: %s, statusCode %d", err, statusCode)) + } + return nil +} + // ValidateIntORString to validate int or string func ValidateIntORString(errorHandler *utils.ErrorHandler, value string, astring string) error { if value == "" || value == astring { diff --git a/internal/provider/acceptancetests/svm_resource_test.go b/internal/provider/acceptancetests/svm_resource_test.go index 17ab8d96..77cd7a84 100644 --- a/internal/provider/acceptancetests/svm_resource_test.go +++ b/internal/provider/acceptancetests/svm_resource_test.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "os" + "regexp" "testing" ) @@ -13,19 +14,39 @@ func TestAccSvmResource(t *testing.T) { ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { - Config: testAccSvmResourceConfig("tfsvm4"), + Config: testAccSvmResourceConfig("tfsvm4", "test"), Check: resource.ComposeTestCheckFunc( - // Check to see a the Vserver name is correct, + // Check to see the Vserver name is correct, resource.TestCheckResourceAttr("netapp-ontap_svm_resource.example", "name", "tfsvm4"), // Check to see if Ipspace is set correctly resource.TestCheckResourceAttr("netapp-ontap_svm_resource.example", "ipspace", "ansibleIpspace_newname"), // Check that a UUID has been set (we don't know what the vaule is as it changes - resource.TestCheckResourceAttrSet("netapp-ontap_svm_resource.example", "uuid")), + resource.TestCheckResourceAttrSet("netapp-ontap_svm_resource.example", "uuid"), + resource.TestCheckResourceAttr("netapp-ontap_svm_resource.example", "comment", "test")), + }, + // Update a comment + { + Config: testAccSvmResourceConfig("tfsvm4", "carchi8py was here"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("netapp-ontap_svm_resource.example", "comment", "carchi8py was here"), + resource.TestCheckResourceAttr("netapp-ontap_svm_resource.example", "name", "tfsvm4")), + }, + // change SVM name + { + Config: testAccSvmResourceConfig("tfsvm3", "carchi8py was here"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("netapp-ontap_svm_resource.example", "comment", "carchi8py was here"), + resource.TestCheckResourceAttr("netapp-ontap_svm_resource.example", "name", "tfsvm3")), + }, + // Fail if the name already exist + { + Config: testAccSvmResourceConfig("svm5", "carchi8py was here"), + ExpectError: regexp.MustCompile("13434908"), }, }, }) } -func testAccSvmResourceConfig(vserver string) string { +func testAccSvmResourceConfig(vserver, comment string) string { host := os.Getenv("TF_ACC_NETAPP_HOST") admin := os.Getenv("TF_ACC_NETAPP_USER") password := os.Getenv("TF_ACC_NETAPP_PASS") @@ -50,11 +71,11 @@ resource "netapp-ontap_svm_resource" "example" { cx_profile_name = "cluster4" name = "%s" ipspace = "ansibleIpspace_newname" - comment = "test" + comment = "%s" snapshot_policy = "default-1weekly" //subtype = "dp_destination" language = "en_us.utf_8" aggregates = ["aggr2"] max_volumes = "200" -}`, host, admin, password, vserver) +}`, host, admin, password, vserver, comment) } diff --git a/internal/provider/svm_resource.go b/internal/provider/svm_resource.go index eea1ce05..cf69df0b 100644 --- a/internal/provider/svm_resource.go +++ b/internal/provider/svm_resource.go @@ -223,10 +223,15 @@ func (r *SvmResource) Read(ctx context.Context, req resource.ReadRequest, resp * // error reporting done inside NewClient return } - _, err = interfaces.GetSvm(errorHandler, *client, data.UUID.ValueString()) + svm, err := interfaces.GetSvm(errorHandler, *client, data.UUID.ValueString()) if err != nil { return } + if svm == nil { + errorHandler.MakeAndReportError("No Svm found", fmt.Sprintf("No SVM found")) + } + data.Name = types.StringValue(svm.Name) + data.UUID = types.StringValue(svm.UUID) // Save updated data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) @@ -238,13 +243,83 @@ func (r *SvmResource) Update(ctx context.Context, req resource.UpdateRequest, re // Read Terraform plan data into the model resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) - + errorHandler := utils.NewErrorHandler(ctx, &resp.Diagnostics) if resp.Diagnostics.HasError() { return } + if data.UUID.IsNull() { + errorHandler.MakeAndReportError("UUID is Null", "UUID is NUll") + } + + var request interfaces.SvmResourceModel + request.Name = data.Name.ValueString() + // TODO: Ipspace can not be modify on SVM/patch. We can't fail or maybe a warning should be sent? + //if !data.Ipspace.IsNull() { + // request.Ipspace.Name = data.Ipspace.ValueString() + //} + + if !data.SnapshotPolicy.IsNull() { + request.SnapshotPolicy.Name = data.SnapshotPolicy.ValueString() + } + + if !data.SubType.IsNull() { + request.SubType = data.SubType.ValueString() + } + + if !data.Comment.IsNull() { + request.Comment = data.Comment.ValueString() + } + + if !data.Language.IsNull() { + request.Language = data.Language.ValueString() + } + + if !data.MaxVolumes.IsNull() { + err := interfaces.ValidateIntORString(errorHandler, data.MaxVolumes.ValueString(), "unlimited") + if err != nil { + return + } + request.MaxVolumes = data.MaxVolumes.ValueString() + } + + if len(data.Aggregates) != 0 { + aggregates := []interfaces.Aggregate{} + for _, v := range data.Aggregates { + aggr := interfaces.Aggregate{} + aggr.Name = v.ValueString() + aggregates = append(aggregates, aggr) + } + err := mapstructure.Decode(aggregates, &request.Aggregates) + if err != nil { + errorHandler.MakeAndReportError("error creating vserver", fmt.Sprintf("error on encoding aggregates info: %s, aggregates %#v", err, aggregates)) + return + } + } + client, err := getRestClient(errorHandler, r.config, data.CxProfileName) + if err != nil { + return + } + // We need to check to see if the name has changed for a rename + rename := false + existingSvm, err := interfaces.GetSvm(errorHandler, *client, data.UUID.ValueString()) + if err != nil { + return + } + if existingSvm.Name != request.Name { + rename = true + } + err = interfaces.UpdateSvm(errorHandler, *client, request, data.UUID.ValueString(), rename) + if err != nil { + return + } + data.ID = types.StringValue(data.UUID.ValueString()) + // Save updated data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } } // Delete deletes the resource and removes the Terraform state on success.