diff --git a/docs/data-sources/networking_ip_interface_data_source.md b/docs/data-sources/networking_ip_interface_data_source.md index 9dfba188..6d754de0 100644 --- a/docs/data-sources/networking_ip_interface_data_source.md +++ b/docs/data-sources/networking_ip_interface_data_source.md @@ -42,6 +42,7 @@ data "netapp-ontap_network_ip_interface" "ip_interface" { - `ip` (Attributes) (see [below for nested schema](#nestedatt--ip)) - `location` (Attributes) (see [below for nested schema](#nestedatt--location)) - `scope` (String) IPInterface scope +- `service_policy` (String) IPInterface service policy diff --git a/docs/data-sources/networking_ip_interfaces_data_source.md b/docs/data-sources/networking_ip_interfaces_data_source.md index bd1f0656..998c17dd 100644 --- a/docs/data-sources/networking_ip_interfaces_data_source.md +++ b/docs/data-sources/networking_ip_interfaces_data_source.md @@ -67,6 +67,7 @@ Read-Only: - `name` (String) IPInterface name - `scope` (String) IPInterface scope - `svm_name` (String) IPInterface svm name. Applies only to SVM-scoped objects +- `service_policy` (String) IPInterface service policy diff --git a/docs/resources/network_ip_interface_resource.md b/docs/resources/network_ip_interface_resource.md index 1dd2ceac..ce0cc3ec 100644 --- a/docs/resources/network_ip_interface_resource.md +++ b/docs/resources/network_ip_interface_resource.md @@ -38,6 +38,7 @@ resource "netapp-ontap_network_ip_interface" "example" { home_port = "e0d" home_node = "ontap_cluster_1-01" } + service_policy = "default-management" } ``` @@ -52,6 +53,10 @@ resource "netapp-ontap_network_ip_interface" "example" { - `name` (String) IPInterface name - `svm_name` (String) IPInterface svm name +### Optional + +- `service_policy` (String) IPInterface service policy + ### Read-Only - `id` (String) IPInterface UUID diff --git a/examples/resources/netapp-ontap_network_ip_interface/resource.tf b/examples/resources/netapp-ontap_network_ip_interface/resource.tf index 97f2651b..ef706352 100644 --- a/examples/resources/netapp-ontap_network_ip_interface/resource.tf +++ b/examples/resources/netapp-ontap_network_ip_interface/resource.tf @@ -1,14 +1,15 @@ resource "netapp-ontap_network_ip_interface" "ip_interface" { # required to know which system to interface with cx_profile_name = "cluster4" - name = "testme" - svm_name = "ansibleSVM" + name = "testme" + svm_name = "ansibleSVM" ip = { address = "10.10.10.10" netmask = 20 - } + } location = { home_port = "e0c" home_node = "ontap_cluster_1-01" } + service_policy = "default-management" } diff --git a/internal/interfaces/networking_ip_interface.go b/internal/interfaces/networking_ip_interface.go index f5d562e5..d1c22da8 100644 --- a/internal/interfaces/networking_ip_interface.go +++ b/internal/interfaces/networking_ip_interface.go @@ -11,12 +11,13 @@ import ( // IPInterfaceGetDataModelONTAP describes the GET record data model using go types for mapping. type IPInterfaceGetDataModelONTAP struct { - Name string `mapstructure:"name"` - Scope string `mapstructure:"scope"` - SVM IPInterfaceSvmName `mapstructure:"svm"` - UUID string `mapstructure:"uuid"` - IP IPInterfaceGetIP `mapstructure:"ip"` - Location IPInterfaceResourceLocation `mapstructure:"location"` + IP IPInterfaceGetIP `mapstructure:"ip"` + Location IPInterfaceResourceLocation `mapstructure:"location"` + Name string `mapstructure:"name"` + Scope string `mapstructure:"scope"` + ServicePolicy IPInterfaceServicePolicy `mapstructure:"service_policy"` + SVM IPInterfaceSvmName `mapstructure:"svm"` + UUID string `mapstructure:"uuid"` } // IPInterfaceGetIP describes the GET record data for IP. @@ -27,10 +28,11 @@ type IPInterfaceGetIP struct { // IPInterfaceResourceBodyDataModelONTAP describes the body data model using go types for mapping. type IPInterfaceResourceBodyDataModelONTAP struct { - Name string `mapstructure:"name"` - SVM IPInterfaceSvmName `mapstructure:"svm,omitempty"` // API errors if body contains svm name when updating. can not use universal 'svm struct' - IP IPInterfaceResourceIP `mapstructure:"ip"` - Location IPInterfaceResourceLocation `mapstructure:"location"` + IP IPInterfaceResourceIP `mapstructure:"ip"` + Location IPInterfaceResourceLocation `mapstructure:"location"` + Name string `mapstructure:"name"` + ServicePolicy IPInterfaceServicePolicy `mapstructure:"service_policy"` + SVM IPInterfaceSvmName `mapstructure:"svm,omitempty"` // API errors if body contains svm name when updating. can not use universal 'svm struct' } // IPInterfaceSvmName describes the svm name specifcally for network ip interface. @@ -61,6 +63,11 @@ type IPInterfaceResourceHomePort struct { Node IPInterfaceResourceHomeNode `mapstructure:"node"` } +// IPInterfaceServicePolicy is the body data model for the service_policy field +type IPInterfaceServicePolicy struct { + Name string `mapstructure:"name,omitempty"` +} + // IPInterfaceDataSourceFilterModel describes filter model. type IPInterfaceDataSourceFilterModel struct { Name string `tfsdk:"name"` @@ -78,7 +85,14 @@ func GetIPInterface(errorHandler *utils.ErrorHandler, r restclient.RestClient, i // query.Set("svm.name", svmName) // query.Set("scope", "svm") // } - query.Fields([]string{"name", "svm.name", "ip", "scope", "location"}) + query.Fields([]string{ + "name", + "svm.name", + "ip", + "scope", + "location", + "service_policy", + }) statusCode, response, err := r.GetNilOrOneRecord(api, query, nil) if err == nil && response == nil { err = fmt.Errorf("no response for GET %s", api) @@ -107,7 +121,14 @@ func GetIPInterfaceByName(errorHandler *utils.ErrorHandler, r restclient.RestCli query.Set("svm.name", svmName) query.Set("scope", "svm") } - query.Fields([]string{"name", "svm.name", "ip", "scope", "location"}) + query.Fields([]string{ + "name", + "svm.name", + "ip", + "scope", + "location", + "service_policy", + }) statusCode, response, err := r.GetNilOrOneRecord(api, query, nil) if err == nil && response == nil { err = fmt.Errorf("no response for GET %s", api) @@ -129,7 +150,14 @@ func GetIPInterfaceByName(errorHandler *utils.ErrorHandler, r restclient.RestCli func GetListIPInterfaces(errorHandler *utils.ErrorHandler, r restclient.RestClient, filter *IPInterfaceDataSourceFilterModel) ([]IPInterfaceGetDataModelONTAP, error) { api := "network/ip/interfaces" query := r.NewQuery() - query.Fields([]string{"name", "svm.name", "ip", "scope", "location"}) + query.Fields([]string{ + "name", + "svm.name", + "ip", + "scope", + "location", + "service_policy", + }) if filter != nil { if filter.Name != "" { diff --git a/internal/provider/networking/network_ip_interface_data_source.go b/internal/provider/networking/network_ip_interface_data_source.go index ac9460be..c6af3b61 100644 --- a/internal/provider/networking/network_ip_interface_data_source.go +++ b/internal/provider/networking/network_ip_interface_data_source.go @@ -5,13 +5,12 @@ import ( "fmt" "strconv" - "github.com/netapp/terraform-provider-netapp-ontap/internal/provider/connection" - "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/netapp/terraform-provider-netapp-ontap/internal/interfaces" + "github.com/netapp/terraform-provider-netapp-ontap/internal/provider/connection" "github.com/netapp/terraform-provider-netapp-ontap/internal/utils" ) @@ -49,6 +48,7 @@ type IPInterfaceDataSourceModel struct { Scope types.String `tfsdk:"scope"` IP *IPDataSourceModel `tfsdk:"ip"` Location *LocationDataSourceModel `tfsdk:"location"` + ServicePolicy types.String `tfsdk:"service_policy"` } // IPDataSourceModel describes the data source model for IP address and mask. @@ -117,6 +117,10 @@ func (d *IPInterfaceDataSource) Schema(ctx context.Context, req datasource.Schem }, Computed: true, }, + "service_policy": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "IPInterface service policy", + }, }, } } @@ -182,6 +186,7 @@ func (d *IPInterfaceDataSource) Read(ctx context.Context, req datasource.ReadReq HomeNode: types.StringValue(restInfo.Location.HomeNode.Name), HomePort: types.StringValue(restInfo.Location.HomePort.Name), } + data.ServicePolicy = types.StringValue(restInfo.ServicePolicy.Name) // Write logs using the tflog package // Documentation: https://terraform.io/plugin/log diff --git a/internal/provider/networking/network_ip_interface_resource.go b/internal/provider/networking/network_ip_interface_resource.go index 5755f342..9d0e8d67 100644 --- a/internal/provider/networking/network_ip_interface_resource.go +++ b/internal/provider/networking/network_ip_interface_resource.go @@ -6,8 +6,6 @@ import ( "strconv" "strings" - "github.com/netapp/terraform-provider-netapp-ontap/internal/provider/connection" - "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -16,6 +14,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/netapp/terraform-provider-netapp-ontap/internal/interfaces" + "github.com/netapp/terraform-provider-netapp-ontap/internal/provider/connection" "github.com/netapp/terraform-provider-netapp-ontap/internal/utils" ) @@ -65,6 +64,7 @@ type IPInterfaceResourceModel struct { SVMName types.String `tfsdk:"svm_name"` IP *IPInterfaceResourceIP `tfsdk:"ip"` Location *IPInterfaceResourceLocation `tfsdk:"location"` + ServicePolicy types.String `tfsdk:"service_policy"` UUID types.String `tfsdk:"id"` } @@ -121,6 +121,14 @@ func (r *IPInterfaceResource) Schema(ctx context.Context, req resource.SchemaReq }, Required: true, }, + "service_policy": schema.StringAttribute{ + MarkdownDescription: "IPInterface service policy", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, "id": schema.StringAttribute{ MarkdownDescription: "IPInterface UUID", Computed: true, @@ -202,6 +210,9 @@ func (r *IPInterfaceResource) Read(ctx context.Context, req resource.ReadRequest } ip.Netmask = types.Int64Value(int64(intValue)) data.IP = &ip + + data.ServicePolicy = types.StringValue(restInfo.ServicePolicy.Name) + // Write logs using the tflog package // Documentation: https://terraform.io/plugin/log tflog.Debug(ctx, fmt.Sprintf("read a resource: %#v", data)) @@ -238,6 +249,7 @@ func (r *IPInterfaceResource) Create(ctx context.Context, req resource.CreateReq body.Location.HomeNode = interfaces.IPInterfaceResourceHomeNode{ Name: data.Location.HomeNode.ValueString(), } + body.ServicePolicy.Name = data.ServicePolicy.ValueString() client, err := connection.GetRestClient(errorHandler, r.config, data.CxProfileName) if err != nil { @@ -254,6 +266,16 @@ func (r *IPInterfaceResource) Create(ctx context.Context, req resource.CreateReq tflog.Trace(ctx, fmt.Sprintf("created a resource, UUID=%s", data.UUID)) + if data.ServicePolicy.IsUnknown() { + // read newly created interface to populate service policy (not fetched by CreateIPInterface) + restInfo, err := interfaces.GetIPInterface(errorHandler, *client, data.UUID.ValueString()) + if err != nil { + // error reporting done inside GetIPInterface + return + } + data.ServicePolicy = types.StringValue(restInfo.ServicePolicy.Name) + } + // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } @@ -284,6 +306,7 @@ func (r *IPInterfaceResource) Update(ctx context.Context, req resource.UpdateReq body.Location.HomeNode = interfaces.IPInterfaceResourceHomeNode{ Name: data.Location.HomeNode.ValueString(), } + body.ServicePolicy.Name = data.ServicePolicy.ValueString() client, err := connection.GetRestClient(errorHandler, r.config, data.CxProfileName) if err != nil { diff --git a/internal/provider/networking/network_ip_interface_resource_alias_test.go b/internal/provider/networking/network_ip_interface_resource_alias_test.go index a7c4d0d5..fef70a61 100644 --- a/internal/provider/networking/network_ip_interface_resource_alias_test.go +++ b/internal/provider/networking/network_ip_interface_resource_alias_test.go @@ -18,28 +18,30 @@ func TestAccNetworkIpInterfaceResourceAlias(t *testing.T) { Steps: []resource.TestStep{ // non-existant SVM return code 2621462. Must happen before create/read { - Config: testAccNetworkIPInterfaceResourceConfigAlias("non-existant", "10.10.10.10", "ontap_cluster_1-01"), + Config: testAccNetworkIPInterfaceResourceConfigAlias("non-existant", "10.10.10.10", "ontap_cluster_1-01", "default-data-files"), ExpectError: regexp.MustCompile("2621462"), }, // non-existant home node { - Config: testAccNetworkIPInterfaceResourceConfigAlias("terraform", "10.10.10.10", "non-existant_home_node"), - ExpectError: regexp.MustCompile("393271"), + Config: testAccNetworkIPInterfaceResourceConfigAlias("terraform", "10.10.10.10", "non-existant_home_node", "default-data-files"), + ExpectError: regexp.MustCompile("53281680"), }, // Create and Read // { - // Config: testAccNetworkIPInterfaceResourceConfigAlias("terraform", "10.10.10.10", "ontap_cluster_1-01"), + // Config: testAccNetworkIPInterfaceResourceConfigAlias("terraform", "10.10.10.10", "ontap_cluster_1-01", "default-data-files"), // Check: resource.ComposeTestCheckFunc( // resource.TestCheckResourceAttr("netapp-ontap_networking_ip_interface_resource.example", "name", "test-interface"), // resource.TestCheckResourceAttr("netapp-ontap_networking_ip_interface_resource.example", "svm_name", "terraform"), + // resource.TestCheckResourceAttr("netapp-ontap_networking_ip_interface_resource.example", "service_policy", "default-data-files"), // ), // }, // // Update and Read // { - // Config: testAccNetworkIPInterfaceResourceConfigAlias("terraform", "10.10.10.20", "ontap_cluster_1-01"), + // Config: testAccNetworkIPInterfaceResourceConfigAlias("terraform", "10.10.10.20", "ontap_cluster_1-01", "default-data-iscsi"), // Check: resource.ComposeTestCheckFunc( // resource.TestCheckResourceAttr("netapp-ontap_networking_ip_interface_resource.example", "name", "test-interface"), // resource.TestCheckResourceAttr("netapp-ontap_networking_ip_interface_resource.example", "ip.address", "10.10.10.20"), + // resource.TestCheckResourceAttr("netapp-ontap_networking_ip_interface_resource.example", "service_policy", "default-data-iscsi"), // ), // }, // Test importing a resource @@ -56,7 +58,7 @@ func TestAccNetworkIpInterfaceResourceAlias(t *testing.T) { }) } -func testAccNetworkIPInterfaceResourceConfigAlias(svmName, address, homeNode string) string { +func testAccNetworkIPInterfaceResourceConfigAlias(svmName, address, homeNode, servicePolicy string) string { host := os.Getenv("TF_ACC_NETAPP_HOST5") admin := os.Getenv("TF_ACC_NETAPP_USER") password := os.Getenv("TF_ACC_NETAPP_PASS2") @@ -89,6 +91,7 @@ resource "netapp-ontap_networking_ip_interface_resource" "example" { home_port = "e0d" home_node = "%s" } + service_policy = "%s" } -`, host, admin, password, svmName, address, homeNode) +`, host, admin, password, svmName, address, homeNode, servicePolicy) } diff --git a/internal/provider/networking/network_ip_interface_resource_test.go b/internal/provider/networking/network_ip_interface_resource_test.go index 185dab69..c2f0d0f3 100644 --- a/internal/provider/networking/network_ip_interface_resource_test.go +++ b/internal/provider/networking/network_ip_interface_resource_test.go @@ -18,28 +18,30 @@ func TestAccNetworkIpInterfaceResource(t *testing.T) { Steps: []resource.TestStep{ // non-existant SVM return code 2621462. Must happen before create/read { - Config: testAccNetworkIPInterfaceResourceConfig("non-existant", "10.10.10.10", "ontap_cluster_1-01"), + Config: testAccNetworkIPInterfaceResourceConfig("non-existant", "10.10.10.10", "ontap_cluster_1-01", "default-data-files"), ExpectError: regexp.MustCompile("2621462"), }, // non-existant home node { - Config: testAccNetworkIPInterfaceResourceConfig("terraform", "10.10.10.10", "non-existant_home_node"), - ExpectError: regexp.MustCompile("393271"), + Config: testAccNetworkIPInterfaceResourceConfig("terraform", "10.10.10.10", "non-existant_home_node", "default-data-files"), + ExpectError: regexp.MustCompile("53281680"), }, // Create and Read // { - // Config: testAccNetworkIPInterfaceResourceConfig("svm0", "10.10.10.10", "ontap_cluster_1-01"), + // Config: testAccNetworkIPInterfaceResourceConfig("svm0", "10.10.10.10", "ontap_cluster_1-01", "default-data-files"), // Check: resource.ComposeTestCheckFunc( // resource.TestCheckResourceAttr("netapp-ontap_network_ip_interface.example", "name", "test-interface"), // resource.TestCheckResourceAttr("netapp-ontap_network_ip_interface.example", "svm_name", "svm0"), + // resource.TestCheckResourceAttr("netapp-ontap_network_ip_interface.example", "service_policy", "default-data-files"), // ), // }, // // Update and Read // { - // Config: testAccNetworkIPInterfaceResourceConfig("svm0", "10.10.10.20", "ontap_cluster_1-01"), + // Config: testAccNetworkIPInterfaceResourceConfig("svm0", "10.10.10.20", "ontap_cluster_1-01", "default-data-iscsi"), // Check: resource.ComposeTestCheckFunc( // resource.TestCheckResourceAttr("netapp-ontap_network_ip_interface.example", "name", "test-interface"), // resource.TestCheckResourceAttr("netapp-ontap_network_ip_interface.example", "ip.address", "10.10.10.20"), + // resource.TestCheckResourceAttr("netapp-ontap_network_ip_interface.example", "service_policy", "default-data-iscsi"), // ), // }, // // Test importing a resource @@ -56,7 +58,7 @@ func TestAccNetworkIpInterfaceResource(t *testing.T) { }) } -func testAccNetworkIPInterfaceResourceConfig(svmName, address, homeNode string) string { +func testAccNetworkIPInterfaceResourceConfig(svmName, address, homeNode, servicePolicy string) string { host := os.Getenv("TF_ACC_NETAPP_HOST5") admin := os.Getenv("TF_ACC_NETAPP_USER") password := os.Getenv("TF_ACC_NETAPP_PASS2") @@ -89,6 +91,7 @@ resource "netapp-ontap_network_ip_interface" "example" { home_port = "e0d" home_node = "%s" } + service_policy = "%s" } -`, host, admin, password, svmName, address, homeNode) +`, host, admin, password, svmName, address, homeNode, servicePolicy) } diff --git a/internal/provider/networking/network_ip_interfaces_data_source.go b/internal/provider/networking/network_ip_interfaces_data_source.go index 09eb4402..420d71cf 100644 --- a/internal/provider/networking/network_ip_interfaces_data_source.go +++ b/internal/provider/networking/network_ip_interfaces_data_source.go @@ -5,13 +5,12 @@ import ( "fmt" "strconv" - "github.com/netapp/terraform-provider-netapp-ontap/internal/provider/connection" - "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/netapp/terraform-provider-netapp-ontap/internal/interfaces" + "github.com/netapp/terraform-provider-netapp-ontap/internal/provider/connection" "github.com/netapp/terraform-provider-netapp-ontap/internal/utils" ) @@ -133,6 +132,10 @@ func (d *IPInterfacesDataSource) Schema(ctx context.Context, req datasource.Sche }, Computed: true, }, + "service_policy": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "IPInterface service policy", + }, }, }, Computed: true, @@ -199,6 +202,7 @@ func (d *IPInterfacesDataSource) Read(ctx context.Context, req datasource.ReadRe Name: types.StringValue(record.Name), Scope: types.StringValue(record.Scope), SVMName: types.StringValue(record.SVM.Name), + ServicePolicy: types.StringValue(record.ServicePolicy.Name), } intNetmask, err := strconv.Atoi(record.IP.Netmask) if err != nil {