Skip to content

Commit

Permalink
Merge pull request #100 from NetApp/6-enhancement-netapp-ontap_svm-ne…
Browse files Browse the repository at this point in the history
…eds-to-support-import

SVM Import feature #6
  • Loading branch information
carchi8py authored Nov 21, 2023
2 parents 28bd7d9 + 9b0871d commit 4a88386
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 26 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ 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_resource**: Add support for import ([#41](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/41))
* **netapp-ontap_protocols_nfs_service_resource**: Add support for import ([#36](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/36))
* **netapp-ontap_svm_resource**: Add support for import ([#6](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/6))


## 1.0.2 (2023-11-17)
Expand Down
55 changes: 54 additions & 1 deletion docs/resources/svm_resource.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,57 @@ resource "netapp-ontap_svm_resource" "example" {
- `id` (String) svm identifier

## Import
Import is currently not support for this Resource.
This resource supports import, which allows you to import existing svms into the state of this resource.
Import require a unique ID composed of the svm name, and connection profile, separated by a comma.

id = `name`,`cx_profile_name`

### Terraform Import

For example
```shell
terraform import netapp-ontap_storage_svm_resource.example svm1,cluster5
```
!> 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_svm_resource.svm_import
id = "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 "svm1_root,svm1,cluster4"
resource "netapp-ontap_storage_svm_resource" "svm_import" {
aggregates = [
{
name = "aggr1"
},
{
name = "aggr2"
},
{
name = "aggr3"
},
]
comment = "testing import"
cx_profile_name = "cluster4"
ipspace = "Default"
language = "c.utf_8"
max_volumes = "unlimited"
name = "testImport"
snapshot_policy = "default"
subtype = "default"
}
```
8 changes: 4 additions & 4 deletions internal/interfaces/svm.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,13 @@ type SvmDataSourceFilterModel struct {
}

// GetSvm to get svm info by uuid
func GetSvm(errorHandler *utils.ErrorHandler, r restclient.RestClient, uuid string) (*SvmGetDataModelONTAP, error) {
func GetSvm(errorHandler *utils.ErrorHandler, r restclient.RestClient, uuid string) (*SvmGetDataSourceModel, error) {
statusCode, response, err := r.GetNilOrOneRecord("svm/svms/"+uuid, nil, nil)
if err != nil {
return nil, errorHandler.MakeAndReportError("error reading svm info", fmt.Sprintf("error on GET svm/svms: %s, statusCode %d", err, statusCode))
}

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", fmt.Sprintf("error: %s, statusCode %d, response %#v", err, statusCode, response))
}
Expand All @@ -78,15 +78,15 @@ 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)
if err != nil {
return nil, errorHandler.MakeAndReportError("error reading svm info", fmt.Sprintf("error on GET svm/svms: %s, statusCode %d", err, statusCode))
}

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))
}
Expand Down
101 changes: 83 additions & 18 deletions internal/provider/svm_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package provider
import (
"context"
"fmt"
"strings"

"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
Expand Down Expand Up @@ -35,16 +36,21 @@ type SvmResource struct {

// SvmResourceModel describes the resource data model.
type SvmResourceModel struct {
CxProfileName types.String `tfsdk:"cx_profile_name"`
Name types.String `tfsdk:"name"`
Ipspace types.String `tfsdk:"ipspace"`
SnapshotPolicy types.String `tfsdk:"snapshot_policy"`
SubType types.String `tfsdk:"subtype"`
Comment types.String `tfsdk:"comment"`
Language types.String `tfsdk:"language"`
Aggregates []types.String `tfsdk:"aggregates"`
MaxVolumes types.String `tfsdk:"max_volumes"`
ID types.String `tfsdk:"id"`
CxProfileName types.String `tfsdk:"cx_profile_name"`
Name types.String `tfsdk:"name"`
Ipspace types.String `tfsdk:"ipspace"`
SnapshotPolicy types.String `tfsdk:"snapshot_policy"`
SubType types.String `tfsdk:"subtype"`
Comment types.String `tfsdk:"comment"`
Language types.String `tfsdk:"language"`
Aggregates []Aggregate `tfsdk:"aggregates"`
MaxVolumes types.String `tfsdk:"max_volumes"`
ID types.String `tfsdk:"id"`
}

// Aggregate describes the resource data model.
type Aggregate struct {
Name string `tfsdk:"name"`
}

// Metadata returns the resource type name.
Expand Down Expand Up @@ -87,10 +93,17 @@ func (r *SvmResource) Schema(ctx context.Context, req resource.SchemaRequest, re
MarkdownDescription: "Language to use for svm",
Optional: true,
},
"aggregates": schema.SetAttribute{
ElementType: types.StringType,
MarkdownDescription: "Aggregates to be assigned use for svm",
Optional: true,
"aggregates": schema.SetNestedAttribute{
Required: true,
MarkdownDescription: "List of Aggregates to be assigned use for svm",
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"name": schema.StringAttribute{
MarkdownDescription: "Name of the aggregate",
Required: true,
},
},
},
},
"max_volumes": schema.StringAttribute{
MarkdownDescription: "Maximum number of volumes that can be created on the svm. Expects an integer or unlimited",
Expand Down Expand Up @@ -172,7 +185,7 @@ func (r *SvmResource) Create(ctx context.Context, req resource.CreateRequest, re
aggregates := []interfaces.Aggregate{}
for _, v := range data.Aggregates {
aggr := interfaces.Aggregate{}
aggr.Name = v.ValueString()
aggr.Name = v.Name
aggregates = append(aggregates, aggr)
}
err := mapstructure.Decode(aggregates, &request.Aggregates)
Expand Down Expand Up @@ -221,7 +234,13 @@ func (r *SvmResource) Read(ctx context.Context, req resource.ReadRequest, resp *
return
}
tflog.Debug(ctx, fmt.Sprintf("read a svm resource: %#v", data))
svm, err := interfaces.GetSvm(errorHandler, *client, data.ID.ValueString())
var svm *interfaces.SvmGetDataSourceModel
if data.ID.ValueString() != "" {
svm, err = interfaces.GetSvm(errorHandler, *client, data.ID.ValueString())
} else {
svm, err = interfaces.GetSvmByNameDataSource(errorHandler, *client, data.Name.ValueString())
}
// svm, err := interfaces.GetSvm(errorHandler, *client, data.ID.ValueString())
if err != nil {
return
}
Expand All @@ -230,6 +249,41 @@ func (r *SvmResource) Read(ctx context.Context, req resource.ReadRequest, resp *
}
data.Name = types.StringValue(svm.Name)
data.ID = types.StringValue(svm.UUID)
aggregates := []Aggregate{}

if len(svm.Aggregates) != 0 {
for _, v := range svm.Aggregates {
tflog.Debug(ctx, fmt.Sprintf("HIIIIIIIIII: %#v", v.Name))
aggr := Aggregate{}
aggr.Name = v.Name
aggregates = append(aggregates, aggr)
}
data.Aggregates = aggregates
}

if svm.Comment != "" {
data.Comment = types.StringValue(svm.Comment)
}

if svm.Ipspace.Name != "" {
data.Ipspace = types.StringValue(svm.Ipspace.Name)
}

if svm.SnapshotPolicy.Name != "" {
data.SnapshotPolicy = types.StringValue(svm.SnapshotPolicy.Name)
}

if svm.SubType != "" {
data.SubType = types.StringValue(svm.SubType)
}

if svm.Language != "" {
data.Language = types.StringValue(svm.Language)
}

if svm.MaxVolumes != "" {
data.MaxVolumes = types.StringValue(svm.MaxVolumes)
}

// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
Expand Down Expand Up @@ -316,7 +370,7 @@ func (r *SvmResource) Update(ctx context.Context, req resource.UpdateRequest, re
if len(data.Aggregates) != 0 {
for _, v := range data.Aggregates {
aggr := interfaces.Aggregate{}
aggr.Name = v.ValueString()
aggr.Name = v.Name
aggregates = append(aggregates, aggr)
}
} else {
Expand Down Expand Up @@ -373,5 +427,16 @@ func (r *SvmResource) Delete(ctx context.Context, req resource.DeleteRequest, re

// ImportState imports a resource using ID from terraform import command by calling the Read method.
func (r *SvmResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
idParts := strings.Split(req.ID, ",")

if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" {
resp.Diagnostics.AddError(
"Unexpected Import Identifier",
fmt.Sprintf("Expected import identifier with format: 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("cx_profile_name"), idParts[1])...)
}
25 changes: 22 additions & 3 deletions internal/provider/svm_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,15 @@ func TestAccSvmResource(t *testing.T) {
Config: testAccSvmResourceConfig("svm5", "carchi8py was here", "default"),
ExpectError: regexp.MustCompile("13434908"),
},
// Import and read
{
ResourceName: "netapp-ontap_svm_resource.example",
ImportState: true,
ImportStateId: fmt.Sprintf("%s,%s", "ansibleSVM", "cluster4"),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("netapp-ontap_svm_resource.example", "name", "ansibleSVM"),
),
},
},
})
}
Expand Down Expand Up @@ -94,9 +103,19 @@ resource "netapp-ontap_svm_resource" "example" {
ipspace = "ansibleIpspace_newname"
comment = "%s"
snapshot_policy = "%s"
//subtype = "dp_destination"
subtype = "default"
language = "en_us.utf_8"
aggregates = ["aggr2"]
max_volumes = "200"
aggregates = [
{
name = "aggr1"
},
{
name = "aggr2"
},
{
name = "aggr3"
},
]
max_volumes = "unlimited"
}`, host, admin, password, svm, comment, snapshotPolicy)
}

0 comments on commit 4a88386

Please sign in to comment.