From 99c96676e9dc58174344eba884782f02febf2766 Mon Sep 17 00:00:00 2001 From: Chuyi Ching Date: Mon, 20 Nov 2023 19:25:22 -0800 Subject: [PATCH 1/4] snapshot import --- .../storage_volume_snapshot_resource.md | 41 ++++++++++++++++- internal/interfaces/storage_volume.go | 3 +- .../interfaces/storage_volume_snapshot.go | 3 +- internal/interfaces/svm.go | 6 +++ .../storage_volume_snapshot_resource.go | 46 ++++++++++++++++--- .../storage_volume_snapshot_resource_test.go | 9 ++++ 6 files changed, 99 insertions(+), 9 deletions(-) diff --git a/docs/resources/storage_volume_snapshot_resource.md b/docs/resources/storage_volume_snapshot_resource.md index 06a52ded..71372c56 100644 --- a/docs/resources/storage_volume_snapshot_resource.md +++ b/docs/resources/storage_volume_snapshot_resource.md @@ -49,4 +49,43 @@ resource "netapp-ontap_storage_volume_snapshot_resource" "example" { - `id` (String) storage/volumes/snapshots identifier ## Import -Import is currently not support for this Resource. +This Resource supports import, which allows you to import existing snapshot into the state of this resoruce. +Import require a unique ID composed of the snapshot name, volume_name, svm_name and cx_profile_name, separated by a comma. + + id = `name`,`volume_name`,`svm_name`,`cx_profile_name` + + ### Terraform Import + + For example + ```shell + terraform import netapp-ontap_storage_volume_snapshot_resource.example snapshotname,vol2,svm1,cluster4 + ``` + +!> The terraform import CLI command can only import resources into the state. Importing via the CLI does not generate configuration. If you want to generate the accompanying configuration for imported resources, use the import block instead. + +### Terrafomr Import Block +This requires Terraform 1.5 or higher, and will auto create the configuration for you + +First create the block +```terraform +import { + to = netapp-ontap_storage_volume_snapshot_resource.snapshot_import + id = "snapshot1,vol1,svm1,cluster4" +} +``` +Next run, this will auto create the configuration for you +```shell +terraform plan -generate-config-out=generated.tf +``` +This will generate a file called generated.tf, which will contain the configuration for the imported resource +```terraform +# __generated__ by Terraform +# Please review these resources and move them into your main configuration files. +# __generated__ by Terraform from "snapshot1,vol1,svm1,cluster4" +resource "netapp-ontap_storage_volume_snapshot_resource" "snapshot_import" { + cx_profile_name = "cluster4" + name = "snapshot1" + volume_name = "vol1" + svm_name = "svm1" +} +``` \ No newline at end of file diff --git a/internal/interfaces/storage_volume.go b/internal/interfaces/storage_volume.go index cfaa7759..5f45d700 100644 --- a/internal/interfaces/storage_volume.go +++ b/internal/interfaces/storage_volume.go @@ -193,7 +193,8 @@ func GetUUIDVolumeByName(errorHandler *utils.ErrorHandler, r restclient.RestClie if response == nil { tflog.Debug(errorHandler.Ctx, fmt.Sprintf("Volume %s not found", name)) - return nil, nil + return nil, errorHandler.MakeAndReportError("error reading volume info", + fmt.Sprintf("error on Get %s: volume %s is not found, statusCode %d", api, name, statusCode)) } var dataONTAP NameDataModel if err := mapstructure.Decode(response, &dataONTAP); err != nil { diff --git a/internal/interfaces/storage_volume_snapshot.go b/internal/interfaces/storage_volume_snapshot.go index 1c1b0518..526b86eb 100644 --- a/internal/interfaces/storage_volume_snapshot.go +++ b/internal/interfaces/storage_volume_snapshot.go @@ -96,7 +96,8 @@ func GetStorageVolumeSnapshots(errorHandler *utils.ErrorHandler, r restclient.Re if response == nil { tflog.Debug(errorHandler.Ctx, fmt.Sprintf("snapshot %s not found for volume UUID %s", name, volumeUUID)) - return nil, nil + return nil, errorHandler.MakeAndReportError("error reading snapshot info", + fmt.Sprintf("snapshot %s not found for volume UUID %s", name, volumeUUID)) } var dataONTAP StorageVolumeSnapshotGetDataModelONTAP diff --git a/internal/interfaces/svm.go b/internal/interfaces/svm.go index d9d752ed..8ea573ed 100644 --- a/internal/interfaces/svm.go +++ b/internal/interfaces/svm.go @@ -86,6 +86,12 @@ func GetSvmByName(errorHandler *utils.ErrorHandler, r restclient.RestClient, nam return nil, errorHandler.MakeAndReportError("error reading svm info", fmt.Sprintf("error on GET svm/svms: %s, statusCode %d", err, statusCode)) } + if response == nil { + tflog.Debug(errorHandler.Ctx, fmt.Sprintf("svm %s not found", name)) + return nil, errorHandler.MakeAndReportError("error reading svm info", + fmt.Sprintf("svm %s not found", name)) + } + var dataONTAP *SvmGetDataModelONTAP if err := mapstructure.Decode(response, &dataONTAP); err != nil { return nil, errorHandler.MakeAndReportError("failed to decode response from GET svm by name", fmt.Sprintf("error: %s, statusCode %d, response %#v", err, statusCode, response)) diff --git a/internal/provider/storage_volume_snapshot_resource.go b/internal/provider/storage_volume_snapshot_resource.go index 92dda990..5d8d34a5 100644 --- a/internal/provider/storage_volume_snapshot_resource.go +++ b/internal/provider/storage_volume_snapshot_resource.go @@ -3,6 +3,7 @@ package provider import ( "context" "fmt" + "strings" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -201,15 +202,36 @@ func (r *StorageVolumeSnapshotResource) Read(ctx context.Context, req resource.R if err != nil { return } - snapshot, err := interfaces.GetStorageVolumeSnapshot(errorHandler, *client, volume.UUID, data.ID.ValueString()) - if err != nil { - return + var snapshot *interfaces.StorageVolumeSnapshotGetDataModelONTAP + if data.ID.ValueString() == "" { + snapshot, err = interfaces.GetStorageVolumeSnapshots(errorHandler, *client, data.Name.ValueString(), volume.UUID) + if err != nil { + return + } + data.ID = types.StringValue(snapshot.UUID) + } else { + snapshot, err = interfaces.GetStorageVolumeSnapshot(errorHandler, *client, volume.UUID, data.ID.ValueString()) + if err != nil { + return + } + data.Name = types.StringValue(snapshot.Name) } - data.Name = types.StringValue(snapshot.Name) + if snapshot.Comment != "" { + data.Comment = types.StringValue(snapshot.Comment) + } + if snapshot.ExpiryTime != "" { + data.ExpiryTime = types.StringValue(snapshot.ExpiryTime) + } + if snapshot.SnapmirrorLabel != "" { + data.SnapmirrorLabel = types.StringValue(snapshot.SnapmirrorLabel) + } + if snapshot.SnaplockExpiryTime != "" { + data.SnaplockExpiryTime = types.StringValue(snapshot.SnaplockExpiryTime) + } // Write logs using the tflog package // Documentation: https://terraform.io/plugin/log - tflog.Debug(ctx, fmt.Sprintf("read a data source: %#v", data)) + tflog.Debug(ctx, fmt.Sprintf("read a snapshot data source: %#v", data)) // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) @@ -316,5 +338,17 @@ func (r *StorageVolumeSnapshotResource) Delete(ctx context.Context, req resource // ImportState imports a resource using ID from terraform import command by calling the Read method. func (r *StorageVolumeSnapshotResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) + tflog.Debug(ctx, fmt.Sprintf("import req an volume snapshot resource: %#v", req)) + idParts := strings.Split(req.ID, ",") + if len(idParts) != 4 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" || idParts[3] == "" { + resp.Diagnostics.AddError( + "Unexpected Import Identifier", + fmt.Sprintf("Expected import identifier with format: name,volume_name,svm_name,cx_profile_name. Got: %q", req.ID), + ) + return + } + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("name"), idParts[0])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("volume_name"), idParts[1])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("svm_name"), idParts[2])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("cx_profile_name"), idParts[3])...) } diff --git a/internal/provider/storage_volume_snapshot_resource_test.go b/internal/provider/storage_volume_snapshot_resource_test.go index 0c8cc905..83e29f98 100644 --- a/internal/provider/storage_volume_snapshot_resource_test.go +++ b/internal/provider/storage_volume_snapshot_resource_test.go @@ -39,6 +39,15 @@ func TestAccStorageVolumeSnapshotResource(t *testing.T) { resource.TestCheckResourceAttr("netapp-ontap_storage_volume_snapshot_resource.example", "comment", "new comment"), ), }, + // Test importing a resource + { + ResourceName: "netapp-ontap_storage_volume_snapshot_resource.example", + ImportState: true, + ImportStateId: fmt.Sprintf("%s,%s,%s,%s", "snaptest", "carchi_test_root", "carchi-test", "cluster4"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("netapp-ontap_storage_volume_snapshot_resource.example", "name", "snaptest"), + ), + }, }, }) } From 0a053f57c450fa2cb3dbe52bb3de3f3f804b8522 Mon Sep 17 00:00:00 2001 From: Chuyi Ching Date: Mon, 20 Nov 2023 20:03:58 -0800 Subject: [PATCH 2/4] fix acc test --- internal/provider/protocols_nfs_service_resource_test.go | 5 +++-- internal/provider/storage_volume_snapshot_resource_test.go | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/provider/protocols_nfs_service_resource_test.go b/internal/provider/protocols_nfs_service_resource_test.go index f83a9dfa..8f8bd3aa 100644 --- a/internal/provider/protocols_nfs_service_resource_test.go +++ b/internal/provider/protocols_nfs_service_resource_test.go @@ -2,10 +2,11 @@ package provider import ( "fmt" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "os" "regexp" "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) func TestAccNfsServiceResource(t *testing.T) { @@ -16,7 +17,7 @@ func TestAccNfsServiceResource(t *testing.T) { // Test error { Config: testAccNfsServiceResourceConfig("non-existant", "false"), - ExpectError: regexp.MustCompile("svm non-existant not found."), + ExpectError: regexp.MustCompile("svm non-existant not found"), }, // Create and read { diff --git a/internal/provider/storage_volume_snapshot_resource_test.go b/internal/provider/storage_volume_snapshot_resource_test.go index 83e29f98..0483bd1f 100644 --- a/internal/provider/storage_volume_snapshot_resource_test.go +++ b/internal/provider/storage_volume_snapshot_resource_test.go @@ -17,7 +17,7 @@ func TestAccStorageVolumeSnapshotResource(t *testing.T) { // non-existant SVM return code 2621462. Must happen before create/read { Config: testAccStorageVolumeSnapshotResourceConfig("non-existant", "my comment"), - ExpectError: regexp.MustCompile("Error: No svm found"), + ExpectError: regexp.MustCompile("svm non-existant not found"), }, // Create and read testing { From ca81843e693874e73fd2fe09464be0cae3e400ce Mon Sep 17 00:00:00 2001 From: Chuyi Ching Date: Tue, 21 Nov 2023 10:09:47 -0800 Subject: [PATCH 3/4] add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c7a220b..d7374d16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## 1.1.0 () ENHANCEMENTS: * **netapp-ontap_storage_aggregate_resource**: Add support for import ([#39](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/39)) +* **netapp-ontap_storage_volume_snapshot_resource**: Add support for import ([#42](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/42)) ## 1.0.1 () From 6af9844eace5330e8b880c6c886dcd7216505898 Mon Sep 17 00:00:00 2001 From: Chuyi Ching Date: Tue, 21 Nov 2023 10:21:57 -0800 Subject: [PATCH 4/4] fix conflict --- internal/interfaces/svm.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/interfaces/svm.go b/internal/interfaces/svm.go index 8ea573ed..a265dc1c 100644 --- a/internal/interfaces/svm.go +++ b/internal/interfaces/svm.go @@ -78,7 +78,7 @@ func GetSvm(errorHandler *utils.ErrorHandler, r restclient.RestClient, uuid stri } // GetSvmByName to get svm info by name -func GetSvmByName(errorHandler *utils.ErrorHandler, r restclient.RestClient, name string) (*SvmGetDataModelONTAP, error) { +func GetSvmByName(errorHandler *utils.ErrorHandler, r restclient.RestClient, name string) (*SvmGetDataSourceModel, error) { query := r.NewQuery() query.Add("name", name) statusCode, response, err := r.GetNilOrOneRecord("svm/svms", query, nil) @@ -92,7 +92,7 @@ func GetSvmByName(errorHandler *utils.ErrorHandler, r restclient.RestClient, nam fmt.Sprintf("svm %s not found", name)) } - var dataONTAP *SvmGetDataModelONTAP + var dataONTAP *SvmGetDataSourceModel if err := mapstructure.Decode(response, &dataONTAP); err != nil { return nil, errorHandler.MakeAndReportError("failed to decode response from GET svm by name", fmt.Sprintf("error: %s, statusCode %d, response %#v", err, statusCode, response)) }