From f219bbb9152e1c7b73cdfc30e164bc3ca718a158 Mon Sep 17 00:00:00 2001 From: Chuyi Ching Date: Wed, 29 Nov 2023 19:09:48 -0800 Subject: [PATCH 1/9] add import ip interface resource --- CHANGELOG.md | 1 + .../networking_ip_interface_resource.md | 37 ++++++++++++++- .../interfaces/networking_ip_interface.go | 30 +++++++++++- .../networking_ip_interface_test.go | 2 +- .../networking_ip_interface_data_source.go | 2 +- .../networking_ip_interface_resource.go | 47 +++++++++++++++---- .../networking_ip_interface_resource_test.go | 10 ++++ 7 files changed, 115 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90522fbf..5fc6ea19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ENHANCEMENTS: * **netapp-ontap_svm_resource**: Add support for import ([#6](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/6)) * **netapp-ontap_storage_volume_snapshot_resource**: Add support for import ([#42](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/42)) * **netapp-ontap_cluster_schedule_resource**: Add support for import ([#31](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/31)) +* **netapp-ontap_networking_ip_interface_resource**: Add support for import ([#32](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/32)) ## 1.0.2 (2023-11-17) diff --git a/docs/resources/networking_ip_interface_resource.md b/docs/resources/networking_ip_interface_resource.md index 2f2ba419..cc25d4c1 100644 --- a/docs/resources/networking_ip_interface_resource.md +++ b/docs/resources/networking_ip_interface_resource.md @@ -68,4 +68,39 @@ Required: - `home_port` (String) IPInterface home port ## Import -Import is currently not support for this Resource. +This Resource supports import, which allows you to import existing network ip interface into the state of this resoruce. +Import require a unique ID composed of the interface name, svm_name and cx_profile_name, separated by a comma. + id = `name`,`svm_name`,`cx_profile_name` + ### Terraform Import + For example + ```shell + terraform import netapp-ontap_networking_ip_interface_resource.example if1,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_networking_ip_interface_resource.if_import + id = "if1,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 "if1,svm1,cluster4" +resource "netapp-ontap_networking_ip_interface_resource" "if1_import" { + cx_profile_name = "cluster4" + name = "if1" + svm_name = "svm1" +} +``` diff --git a/internal/interfaces/networking_ip_interface.go b/internal/interfaces/networking_ip_interface.go index 4fdf1f96..f5d562e5 100644 --- a/internal/interfaces/networking_ip_interface.go +++ b/internal/interfaces/networking_ip_interface.go @@ -69,7 +69,35 @@ type IPInterfaceDataSourceFilterModel struct { } // GetIPInterface to get ip_interface info -func GetIPInterface(errorHandler *utils.ErrorHandler, r restclient.RestClient, name string, svmName string) (*IPInterfaceGetDataModelONTAP, error) { +func GetIPInterface(errorHandler *utils.ErrorHandler, r restclient.RestClient, id string) (*IPInterfaceGetDataModelONTAP, error) { + api := "network/ip/interfaces" + "/" + id + query := r.NewQuery() + // if svmName == "" { + // query.Set("scope", "cluster") + // } else { + // query.Set("svm.name", svmName) + // query.Set("scope", "svm") + // } + query.Fields([]string{"name", "svm.name", "ip", "scope", "location"}) + statusCode, response, err := r.GetNilOrOneRecord(api, query, nil) + if err == nil && response == nil { + err = fmt.Errorf("no response for GET %s", api) + } + if err != nil { + return nil, errorHandler.MakeAndReportError("error reading ip_interface info", fmt.Sprintf("error on GET %s: %s, statusCode %d", api, err, statusCode)) + } + + var dataONTAP IPInterfaceGetDataModelONTAP + if err := mapstructure.Decode(response, &dataONTAP); err != nil { + return nil, errorHandler.MakeAndReportError(fmt.Sprintf("failed to decode response from GET %s", api), + fmt.Sprintf("error: %s, statusCode %d, response %#v", err, statusCode, response)) + } + tflog.Debug(errorHandler.Ctx, fmt.Sprintf("Read ip_interface data source: %#v", dataONTAP)) + return &dataONTAP, nil +} + +// GetIPInterfaceByName to get ip_interface info +func GetIPInterfaceByName(errorHandler *utils.ErrorHandler, r restclient.RestClient, name string, svmName string) (*IPInterfaceGetDataModelONTAP, error) { api := "network/ip/interfaces" query := r.NewQuery() query.Set("name", name) diff --git a/internal/interfaces/networking_ip_interface_test.go b/internal/interfaces/networking_ip_interface_test.go index 4ce06bf2..4e83779a 100644 --- a/internal/interfaces/networking_ip_interface_test.go +++ b/internal/interfaces/networking_ip_interface_test.go @@ -130,7 +130,7 @@ func TestGetIPInterface(t *testing.T) { if err != nil { panic(err) } - got, err := GetIPInterface(errorHandler, *r, "name", "svmName") + got, err := GetIPInterfaceByName(errorHandler, *r, "name", "svmName") if err != nil { fmt.Printf("err: %s\n", err) } diff --git a/internal/provider/networking_ip_interface_data_source.go b/internal/provider/networking_ip_interface_data_source.go index 735ab8d8..ebd9f9ab 100644 --- a/internal/provider/networking_ip_interface_data_source.go +++ b/internal/provider/networking_ip_interface_data_source.go @@ -145,7 +145,7 @@ func (d *IPInterfaceDataSource) Read(ctx context.Context, req datasource.ReadReq return } - restInfo, err := interfaces.GetIPInterface(errorHandler, *client, data.Name.ValueString(), data.SVMName.ValueString()) + restInfo, err := interfaces.GetIPInterfaceByName(errorHandler, *client, data.Name.ValueString(), data.SVMName.ValueString()) if err != nil { // error reporting done inside GetIPInterface return diff --git a/internal/provider/networking_ip_interface_resource.go b/internal/provider/networking_ip_interface_resource.go index f0a9ea41..0594c94f 100644 --- a/internal/provider/networking_ip_interface_resource.go +++ b/internal/provider/networking_ip_interface_resource.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "strconv" + "strings" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -162,10 +163,19 @@ func (r *IPInterfaceResource) Read(ctx context.Context, req resource.ReadRequest return } - restInfo, err := interfaces.GetIPInterface(errorHandler, *client, data.Name.ValueString(), data.SVMName.ValueString()) - if err != nil { - // error reporting done inside GetIPInterface - return + var restInfo *interfaces.IPInterfaceGetDataModelONTAP + if data.UUID.IsNull() { + restInfo, err = interfaces.GetIPInterfaceByName(errorHandler, *client, data.Name.ValueString(), data.SVMName.ValueString()) + if err != nil { + // error reporting done inside GetIPInterfaceByName + return + } + } else { + restInfo, err = interfaces.GetIPInterface(errorHandler, *client, data.UUID.ValueString()) + if err != nil { + // error reporting done inside GetIPInterface + return + } } if restInfo == nil { errorHandler.MakeAndReportError("No Interface found", fmt.Sprintf("NO interface, %s found.", data.Name.ValueString())) @@ -173,15 +183,21 @@ func (r *IPInterfaceResource) Read(ctx context.Context, req resource.ReadRequest } data.Name = types.StringValue(restInfo.Name) data.UUID = types.StringValue(restInfo.UUID) - data.Location.HomeNode = types.StringValue(restInfo.Location.HomeNode.Name) - data.IP.Address = types.StringValue(restInfo.IP.Address) + + var location IPInterfaceResourceLocation + location.HomeNode = types.StringValue(restInfo.Location.HomeNode.Name) + location.HomePort = types.StringValue(restInfo.Location.HomePort.Name) + data.Location = &location + + var ip IPInterfaceResourceIP + ip.Address = types.StringValue(restInfo.IP.Address) intValue, err := strconv.Atoi(restInfo.IP.Netmask) if err != nil { errorHandler.MakeAndReportError("Failed to read ip interface", fmt.Sprintf("Error: failed to convert string value '%s' to int for net mask.", restInfo.IP.Netmask)) return } - data.IP.Netmask = types.Int64Value(int64(intValue)) - + ip.Netmask = types.Int64Value(int64(intValue)) + data.IP = &ip // Write logs using the tflog package // Documentation: https://terraform.io/plugin/log tflog.Debug(ctx, fmt.Sprintf("read a resource: %#v", data)) @@ -300,7 +316,7 @@ func (r *IPInterfaceResource) Delete(ctx context.Context, req resource.DeleteReq } if data.UUID.IsNull() { - errorHandler.MakeAndReportError("UUID is null", "ip_interface UUID is null") + errorHandler.MakeAndReportError("UUID is null", "ip_interface ID is null") return } @@ -313,5 +329,16 @@ func (r *IPInterfaceResource) Delete(ctx context.Context, req resource.DeleteReq // ImportState imports a resource using ID from terraform import command by calling the Read method. func (r *IPInterfaceResource) 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 a network ip interface resource: %#v", req)) + idParts := strings.Split(req.ID, ",") + if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" { + resp.Diagnostics.AddError( + "Unexpected Import Identifier", + fmt.Sprintf("Expected import identifier with format: 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("svm_name"), idParts[1])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("cx_profile_name"), idParts[2])...) } diff --git a/internal/provider/networking_ip_interface_resource_test.go b/internal/provider/networking_ip_interface_resource_test.go index 68fd41a1..619f1d0e 100644 --- a/internal/provider/networking_ip_interface_resource_test.go +++ b/internal/provider/networking_ip_interface_resource_test.go @@ -40,6 +40,16 @@ func TestAccNetworkingIpInterfaceResource(t *testing.T) { resource.TestCheckResourceAttr("netapp-ontap_networking_ip_interface_resource.example", "ip.address", "10.10.10.20"), ), }, + // Test importing a resource + { + ResourceName: "netapp-ontap_networking_ip_interface_resource.example", + ImportState: true, + ImportStateId: fmt.Sprintf("%s,%s,%s", "test-interface", "svm0", "cluster4"), + 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"), + ), + }, }, }) } From 68999ce9593e7819ff2e5296d03febcc48822344 Mon Sep 17 00:00:00 2001 From: Chris Archibald Date: Mon, 4 Dec 2023 08:51:10 -0800 Subject: [PATCH 2/9] add blackduck scan --- .github/workflows/blackduck.yml | 21 ++++++++++++++++++ scripts/bd_scan.bash | 39 +++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 .github/workflows/blackduck.yml create mode 100644 scripts/bd_scan.bash diff --git a/.github/workflows/blackduck.yml b/.github/workflows/blackduck.yml new file mode 100644 index 00000000..c4a26b68 --- /dev/null +++ b/.github/workflows/blackduck.yml @@ -0,0 +1,21 @@ +name: Blackduck Scan + +on: + push: + branches: + - integration/main + +jobs: + build: + + runs-on: self-hosted + steps: + - uses: actions/checkout@v3 + + - name: Build + env: + BLACKDUCK_URL: ${{ secrets.BLACKDUCK_URL }} + BLACKDUCK_API_TOKEN: ${{ secrets.$BLACKDUCK_API_TOKEN }} + + run: | + bash pipeline_scripts/bd_scan.bash $BLACKDUCK_URL $BLACKDUCK_API_TOKEN diff --git a/scripts/bd_scan.bash b/scripts/bd_scan.bash new file mode 100644 index 00000000..1fd6400e --- /dev/null +++ b/scripts/bd_scan.bash @@ -0,0 +1,39 @@ + +help_and_exit () { + echo 'This script expects a URL and API_TOKEN to connect to a BD server' + echo "eg: $0 https://blackduck.domain.com N0TAr3AlKeY@tA1L" + exit 1 +} + +# change these 2 values to reflect your project name and version: +export DETECT_PROJECT_NAME="Terraform NetApp ONTAP Provider" +export DETECT_PROJECT_VERSION_NAME=1.1.0 +export DETECT_CODE_LOCATION_NAME="${DETECT_PROJECT_NAME}_${DETECT_PROJECT_VERSION_NAME}_code" +export DETECT_BOM_AGGREGATE_NAME="${DETECT_PROJECT_NAME}_${DETECT_PROJECT_VERSION_NAME}_bom" + +# set this to true for python or yaml. false for go or other compiled language +export DETECT_DETECTOR_BUILDLESS=false + +# additionally as needed +# detect.detector.search.depth +# see https://blackducksoftware.github.io/synopsys-detect/latest/ for help + +# add go path +#export DETECT_GO_PATH="/usr/bin/go" +# add git path +#export DETECT_GIT_PATH="/usr/bin/git" +# add java path +#export DETECT_JAVA_PATH="/usr/software/java/openjdk-11.0.15_10/bin/java" + +if [ -z "$1" ]; then + help_and_exit +fi + +if [ -z "$2" ]; then + help_and_exit +fi + +export BLACKDUCK_URL=$1 +export BLACKDUCK_API_TOKEN=$2 + +bash <(curl -s -L https://detect.synopsys.com/detect7.sh) --blackduck.trust.cert=true \ No newline at end of file From cfddde9cbcb7264b2ec460c0930c39fae1f8ed4a Mon Sep 17 00:00:00 2001 From: Chuyi Ching Date: Mon, 4 Dec 2023 12:33:28 -0800 Subject: [PATCH 3/9] add import rule --- CHANGELOG.md | 1 + ...otocols_nfs_export_policy_rule_resource.md | 36 +++++++++++++++- ...otocols_nfs_export_policy_rule_resource.go | 43 +++++++++++-------- ...ls_nfs_export_policy_rule_resource_test.go | 16 +++++++ 4 files changed, 76 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fc6ea19..d38eec7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ ENHANCEMENTS: * **netapp-ontap_storage_volume_snapshot_resource**: Add support for import ([#42](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/42)) * **netapp-ontap_cluster_schedule_resource**: Add support for import ([#31](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/31)) * **netapp-ontap_networking_ip_interface_resource**: Add support for import ([#32](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/32)) +* **netapp-ontap_protocols_nfs_export_policy_rule_resource**: Add support for import ([#35](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/35)) ## 1.0.2 (2023-11-17) diff --git a/docs/resources/protocols_nfs_export_policy_rule_resource.md b/docs/resources/protocols_nfs_export_policy_rule_resource.md index c3f12f97..21d2a2ff 100644 --- a/docs/resources/protocols_nfs_export_policy_rule_resource.md +++ b/docs/resources/protocols_nfs_export_policy_rule_resource.md @@ -59,5 +59,39 @@ resource "netapp-ontap_protocols_nfs_export_policy_rule_resource" "example" { - `index` (Number) rule index ## Import -Import is currently not support for this Resource. +This Resource supports import, which allows you to import existing nfs export policy rule into the state of this resoruce. +Import require a unique ID composed of the rule index, export policy name, svm_name and cx_profile_name, separated by a comma. + id = `index`,`export_policy_name`,`svm_name`,`cx_profile_name` + ### Terraform Import + For example + ```shell + terraform import netapp-ontap_protocols_nfs_export_policy_rule_resource.rule_import index1,exp1,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_protocols_nfs_export_policy_rule_resource.rule_import + id = "index1,exp1,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 "index1,exp1,svm1,cluster4" +resource "netapp-ontap_protocols_nfs_export_policy_rule_resource" "rule_import" { + cx_profile_name = "cluster4" + export_policy_name = "exp1" + svm_name = "svm1" +} +``` \ No newline at end of file diff --git a/internal/provider/protocols_nfs_export_policy_rule_resource.go b/internal/provider/protocols_nfs_export_policy_rule_resource.go index ef070c0a..75f1b8e9 100644 --- a/internal/provider/protocols_nfs_export_policy_rule_resource.go +++ b/internal/provider/protocols_nfs_export_policy_rule_resource.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "strconv" + "strings" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/path" @@ -354,6 +355,7 @@ func (r *ExportPolicyRuleResource) Read(ctx context.Context, req resource.ReadRe return } exportPolicyID = strconv.Itoa(exportPolicy.ID) + data.ExportPolicyID = types.StringValue(strconv.Itoa(exportPolicy.ID)) } else { exportPolicyID = data.ExportPolicyID.ValueString() } @@ -385,28 +387,18 @@ func (r *ExportPolicyRuleResource) Read(ctx context.Context, req resource.ReadRe data.Protocols = protocols data.Superuser = superuser + data.ID = types.StringValue(fmt.Sprintf("%s_%s_%s_%d", data.CxProfileName.ValueString(), data.SVMName.ValueString(), data.ExportPolicyName.ValueString(), data.Index.ValueInt64())) + for _, e := range restInfo.ClientsMatch { clientsMatch = append(clientsMatch, types.StringValue(e.Match)) } data.ClientsMatch = clientsMatch - - if !data.AllowDeviceCreation.IsNull() { - data.AllowDeviceCreation = types.BoolValue(restInfo.AllowDeviceCreation) - } - - if !data.AllowSuid.IsNull() { - data.AllowSuid = types.BoolValue(restInfo.AllowSuid) - } - - if !data.ChownMode.IsNull() { - data.ChownMode = types.StringValue(restInfo.ChownMode) - } - if !data.NtfsUnixSecurity.IsNull() { - data.NtfsUnixSecurity = types.StringValue(restInfo.NtfsUnixSecurity) - } - if !data.AnonymousUser.IsNull() { - data.AnonymousUser = types.StringValue(restInfo.AnonymousUser) - } + // update optional params containg devaule values with updated values + data.AllowDeviceCreation = types.BoolValue(restInfo.AllowDeviceCreation) + data.AllowSuid = types.BoolValue(restInfo.AllowSuid) + data.ChownMode = types.StringValue(restInfo.ChownMode) + data.NtfsUnixSecurity = types.StringValue(restInfo.NtfsUnixSecurity) + data.AnonymousUser = types.StringValue(restInfo.AnonymousUser) // Save updated data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) @@ -537,5 +529,18 @@ func (r *ExportPolicyRuleResource) Delete(ctx context.Context, req resource.Dele // ImportState imports a resource using ID from terraform import command by calling the Read method. func (r *ExportPolicyRuleResource) 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 a nfs export policy rule 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: index,export_policy_name,svm_name,cx_profile_name. Got: %q", req.ID), + ) + return + } + index, _ := strconv.Atoi(idParts[0]) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("index"), index)...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("export_policy_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/protocols_nfs_export_policy_rule_resource_test.go b/internal/provider/protocols_nfs_export_policy_rule_resource_test.go index 98865965..a734aaa3 100644 --- a/internal/provider/protocols_nfs_export_policy_rule_resource_test.go +++ b/internal/provider/protocols_nfs_export_policy_rule_resource_test.go @@ -42,6 +42,22 @@ func TestAccNFSExportPolicyRuleResource(t *testing.T) { resource.TestMatchResourceAttr("netapp-ontap_protocols_nfs_export_policy_rule_resource.example1", "id", regexp.MustCompile(`carchi-test_default_`)), ), }, + // Test importing a resource + { + ResourceName: "netapp-ontap_protocols_nfs_export_policy_rule_resource.example1", + ImportState: true, + ImportStateId: fmt.Sprintf("%s,%s,%s,%s", "1", "carchi-test", "default", "cluster4"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("netapp-ontap_protocols_nfs_export_policy_rule_resource.example1", "svm_name", "carchi-test"), + resource.TestCheckResourceAttr("netapp-ontap_protocols_nfs_export_policy_rule_resource.example1", "export_policy_name", "default"), + resource.TestCheckResourceAttr("netapp-ontap_protocols_nfs_export_policy_rule_resource.example1", "allow_suid", "true"), + resource.TestCheckTypeSetElemAttr("netapp-ontap_protocols_nfs_export_policy_rule_resource.example1", "protocols.*", "nfs3"), + resource.TestCheckTypeSetElemAttr("netapp-ontap_protocols_nfs_export_policy_rule_resource.example1", "ro_rule.*", "krb5i"), + resource.TestCheckTypeSetElemAttr("netapp-ontap_protocols_nfs_export_policy_rule_resource.example1", "rw_rule.*", "any"), + // check id + resource.TestMatchResourceAttr("netapp-ontap_protocols_nfs_export_policy_rule_resource.example1", "id", regexp.MustCompile(`carchi-test_default_`)), + ), + }, }, }) } From e7392ae4f12631e82c62510f088c990b11029162 Mon Sep 17 00:00:00 2001 From: Chuyi Ching Date: Mon, 4 Dec 2023 13:16:45 -0800 Subject: [PATCH 4/9] fix acc test --- .../provider/protocols_nfs_export_policy_rule_resource_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/provider/protocols_nfs_export_policy_rule_resource_test.go b/internal/provider/protocols_nfs_export_policy_rule_resource_test.go index a734aaa3..8c3318a3 100644 --- a/internal/provider/protocols_nfs_export_policy_rule_resource_test.go +++ b/internal/provider/protocols_nfs_export_policy_rule_resource_test.go @@ -46,7 +46,7 @@ func TestAccNFSExportPolicyRuleResource(t *testing.T) { { ResourceName: "netapp-ontap_protocols_nfs_export_policy_rule_resource.example1", ImportState: true, - ImportStateId: fmt.Sprintf("%s,%s,%s,%s", "1", "carchi-test", "default", "cluster4"), + ImportStateId: fmt.Sprintf("%s,%s,%s,%s", "1", "default", "carchi-test", "cluster4"), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("netapp-ontap_protocols_nfs_export_policy_rule_resource.example1", "svm_name", "carchi-test"), resource.TestCheckResourceAttr("netapp-ontap_protocols_nfs_export_policy_rule_resource.example1", "export_policy_name", "default"), From 34a94297c33fe8128e6460d426564d68ce17f623 Mon Sep 17 00:00:00 2001 From: Chris Archibald Date: Mon, 4 Dec 2023 13:40:05 -0800 Subject: [PATCH 5/9] Update blackduck.yml --- .github/workflows/blackduck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/blackduck.yml b/.github/workflows/blackduck.yml index c4a26b68..9670f75b 100644 --- a/.github/workflows/blackduck.yml +++ b/.github/workflows/blackduck.yml @@ -15,7 +15,7 @@ jobs: - name: Build env: BLACKDUCK_URL: ${{ secrets.BLACKDUCK_URL }} - BLACKDUCK_API_TOKEN: ${{ secrets.$BLACKDUCK_API_TOKEN }} + BLACKDUCK_API_TOKEN: ${{ secrets.BLACKDUCK_API_TOKEN }} run: | bash pipeline_scripts/bd_scan.bash $BLACKDUCK_URL $BLACKDUCK_API_TOKEN From e2b283871663d24cb5e7134cde5f96847f9097b4 Mon Sep 17 00:00:00 2001 From: Chris Archibald Date: Mon, 4 Dec 2023 13:41:01 -0800 Subject: [PATCH 6/9] Update blackduck.yml --- .github/workflows/blackduck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/blackduck.yml b/.github/workflows/blackduck.yml index 9670f75b..bd44b801 100644 --- a/.github/workflows/blackduck.yml +++ b/.github/workflows/blackduck.yml @@ -18,4 +18,4 @@ jobs: BLACKDUCK_API_TOKEN: ${{ secrets.BLACKDUCK_API_TOKEN }} run: | - bash pipeline_scripts/bd_scan.bash $BLACKDUCK_URL $BLACKDUCK_API_TOKEN + bash scripts/bd_scan.bash $BLACKDUCK_URL $BLACKDUCK_API_TOKEN From abaf98b2d6f3a87c220e939fdd3f728ab3c81848 Mon Sep 17 00:00:00 2001 From: Chris Archibald Date: Mon, 4 Dec 2023 13:47:26 -0800 Subject: [PATCH 7/9] Update blackduck.yml --- .github/workflows/blackduck.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/blackduck.yml b/.github/workflows/blackduck.yml index bd44b801..36854a2c 100644 --- a/.github/workflows/blackduck.yml +++ b/.github/workflows/blackduck.yml @@ -10,6 +10,10 @@ jobs: runs-on: self-hosted steps: + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' # See 'Supported distributions' for available options + java-version: '17' - uses: actions/checkout@v3 - name: Build From 41b706c5ff17bdc72284d7fab997c42e6b759f4c Mon Sep 17 00:00:00 2001 From: Chris Archibald Date: Mon, 4 Dec 2023 13:50:11 -0800 Subject: [PATCH 8/9] Update blackduck.yml --- .github/workflows/blackduck.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/blackduck.yml b/.github/workflows/blackduck.yml index 36854a2c..7ee1ad46 100644 --- a/.github/workflows/blackduck.yml +++ b/.github/workflows/blackduck.yml @@ -14,6 +14,10 @@ jobs: with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.19.3' - uses: actions/checkout@v3 - name: Build From f4701ca83f8d7dbc227105e2ceceed906fd99397 Mon Sep 17 00:00:00 2001 From: Suhas Bangalore Shekar Date: Tue, 5 Dec 2023 20:23:59 +0530 Subject: [PATCH 9/9] Snapmirror policy import --- CHANGELOG.md | 1 + docs/resources/snapmirror_policy_resource.md | 56 ++++++++++++++++++- internal/interfaces/snapmirror_policy.go | 6 +- .../provider/snapmirror_policy_resource.go | 29 +++++++++- 4 files changed, 86 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90522fbf..503f4026 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ENHANCEMENTS: * **netapp-ontap_svm_resource**: Add support for import ([#6](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/6)) * **netapp-ontap_storage_volume_snapshot_resource**: Add support for import ([#42](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/42)) * **netapp-ontap_cluster_schedule_resource**: Add support for import ([#31](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/31)) +* **netapp-ontap_snapmiror_policy**: Add support for import ([#38](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/38)) ## 1.0.2 (2023-11-17) diff --git a/docs/resources/snapmirror_policy_resource.md b/docs/resources/snapmirror_policy_resource.md index fbb0d2c7..e98f4d60 100644 --- a/docs/resources/snapmirror_policy_resource.md +++ b/docs/resources/snapmirror_policy_resource.md @@ -119,4 +119,58 @@ Optional: - `prefix` (String) Specifies the prefix for the Snapshot copy name to be created as per the schedule ## Import -Import is currently not support for this Resource. +This resource supports import, which allows you to import existing snapmirror policy into the state of this resource. +Import require a unique ID composed of the snapmirror policy name, svm name and connection profile, separated by a comma. + +id = `name`,`svm_name`,`cx_profile_name` + +### Terraform Import + +For example +```shell + terraform import netapp-ontap_snapmirror_policy_resource.example test_name,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_snapmirror_policy_resource.snapmirror_policy_import + id = "test_name,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_snapmirror_policy_resource" "svm_import" { + comment = "testing import" + cx_profile_name = "cluster4" + name = "testImport" + copy_all_source_snapshots = false + copy_latest_source_snapshot = false + create_snapshot_on_source = false + identity_preservation = full + network_compression_enabled = false + retention = [ + { + count = 4 + creation_schedule_name = null + label = hi + prefix = null + } + ] + svm_name = ansibleSVM + sync_type = null + transfer_schedule_name = null + type = async +} +``` diff --git a/internal/interfaces/snapmirror_policy.go b/internal/interfaces/snapmirror_policy.go index 554646d6..1c8e4624 100644 --- a/internal/interfaces/snapmirror_policy.go +++ b/internal/interfaces/snapmirror_policy.go @@ -130,7 +130,7 @@ func GetSnapmirrorPolicy(errorHandler *utils.ErrorHandler, r restclient.RestClie } // GetSnapmirrorPolicyByName to get snapmirror policy info -func GetSnapmirrorPolicyByName(errorHandler *utils.ErrorHandler, r restclient.RestClient, name string, svmName string) (*SnapmirrorPolicyGetDataModelONTAP, error) { +func GetSnapmirrorPolicyByName(errorHandler *utils.ErrorHandler, r restclient.RestClient, name string, svmName string) (*SnapmirrorPolicyGetRawDataModelONTAP, error) { api := "snapmirror/policies" query := r.NewQuery() query.Set("name", name) @@ -141,7 +141,7 @@ func GetSnapmirrorPolicyByName(errorHandler *utils.ErrorHandler, r restclient.Re query.Set("scope", "svm") } // TODO: copy_all_source_snapshots is 9.10 and up - query.Fields(([]string{"name", "svm.name", "type", "comment", "transfer_schedule", "network_compression_enabled", "retention", "identity_preservation", "copy_all_source_snapshots", "uuid"})) + query.Fields(([]string{"name", "svm.name", "type", "sync_type", "comment", "transfer_schedule", "network_compression_enabled", "retention", "identity_preservation", "copy_all_source_snapshots", "uuid"})) statusCode, response, err := r.GetNilOrOneRecord(api, query, nil) if err == nil && response == nil { err = fmt.Errorf("no response for GET %s", api) @@ -150,7 +150,7 @@ func GetSnapmirrorPolicyByName(errorHandler *utils.ErrorHandler, r restclient.Re return nil, errorHandler.MakeAndReportError("error reading snapmirror/policies info", fmt.Sprintf("error on GET %s: %s, statusCode %d", api, err, statusCode)) } - var dataONTAP SnapmirrorPolicyGetDataModelONTAP + var dataONTAP SnapmirrorPolicyGetRawDataModelONTAP if err := mapstructure.Decode(response, &dataONTAP); err != nil { return nil, errorHandler.MakeAndReportError(fmt.Sprintf("failed to decode response from GET %s", api), fmt.Sprintf("error: %s, statusCode %d, response %#v", err, statusCode, response)) diff --git a/internal/provider/snapmirror_policy_resource.go b/internal/provider/snapmirror_policy_resource.go index 9b31b710..683ef5c9 100644 --- a/internal/provider/snapmirror_policy_resource.go +++ b/internal/provider/snapmirror_policy_resource.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "strconv" + "strings" "github.com/hashicorp/terraform-plugin-framework-validators/boolvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" @@ -255,7 +256,12 @@ func (r *SnapmirrorPolicyResource) Read(ctx context.Context, req resource.ReadRe return } - restInfo, err := interfaces.GetSnapmirrorPolicy(errorHandler, *client, data.ID.ValueString()) + var restInfo *interfaces.SnapmirrorPolicyGetRawDataModelONTAP + if data.ID.ValueString() != "" { + restInfo, err = interfaces.GetSnapmirrorPolicy(errorHandler, *client, data.ID.ValueString()) + } else { + restInfo, err = interfaces.GetSnapmirrorPolicyByName(errorHandler, *client, data.Name.ValueString(), data.SVMName.ValueString()) + } if err != nil { // error reporting done inside GETSnapmirrorPolicy return @@ -269,10 +275,17 @@ func (r *SnapmirrorPolicyResource) Read(ctx context.Context, req resource.ReadRe if restInfo.SyncType != "" { data.SyncType = types.StringValue(restInfo.SyncType) } + if restInfo.Comment != "" { + data.Comment = types.StringValue(restInfo.Comment) + } + if restInfo.IdentityPreservation != "" { + data.IdentityPreservation = types.StringValue(restInfo.IdentityPreservation) + } data.CopyAllSourceSnapshots = types.BoolValue(restInfo.CopyAllSourceSnapshots) data.NetworkCompressionEnabled = types.BoolValue(restInfo.NetworkCompressionEnabled) data.CopyLatestSourceSnapshot = types.BoolValue(restInfo.CopyLatestSourceSnapshot) data.CreateSnapshotOnSource = types.BoolValue(restInfo.CreateSnapshotOnSource) + data.ID = types.StringValue(restInfo.UUID) // if len(restInfo.Retention) == 0 { if restInfo.Retention == nil { @@ -621,5 +634,17 @@ func (r *SnapmirrorPolicyResource) Delete(ctx context.Context, req resource.Dele // ImportState imports a resource using ID from terraform import command by calling the Read method. func (r *SnapmirrorPolicyResource) 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) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" { + resp.Diagnostics.AddError( + "Unexpected Import Identifier", + fmt.Sprintf("Expected import identifier with format: 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("svm_name"), idParts[1])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("cx_profile_name"), idParts[2])...) }