From e07c2972997a50a9c451162193adca1c3712258c Mon Sep 17 00:00:00 2001 From: Chris Archibald Date: Tue, 15 Aug 2023 17:21:11 +0000 Subject: [PATCH] Sync bitbucket and GitHub --- .../storage_snapshot_policies_data_source.md | 85 +++++++ .../storage_snapshot_policy_data_source.md | 59 +++++ .../data-source.tf | 7 + .../provider.tf | 44 ++++ .../variables.tf | 11 + .../data-source.tf | 5 + .../provider.tf | 44 ++++ .../variables.tf | 11 + .../interfaces/storage_snapshot_policy.go | 33 ++- .../storage_snapshot_policy_test.go | 162 ++++++++++++- internal/provider/provider.go | 2 + .../storage_snapshot_policies_data_source.go | 222 ++++++++++++++++++ .../storage_snapshot_policy_data_source.go | 190 +++++++++++++++ 13 files changed, 872 insertions(+), 3 deletions(-) create mode 100644 docs/data-sources/storage_snapshot_policies_data_source.md create mode 100644 docs/data-sources/storage_snapshot_policy_data_source.md create mode 100644 examples/data-sources/netapp-ontap_storage_snapshot_policies/data-source.tf create mode 100644 examples/data-sources/netapp-ontap_storage_snapshot_policies/provider.tf create mode 100644 examples/data-sources/netapp-ontap_storage_snapshot_policies/variables.tf create mode 100644 examples/data-sources/netapp-ontap_storage_snapshot_policy/data-source.tf create mode 100644 examples/data-sources/netapp-ontap_storage_snapshot_policy/provider.tf create mode 100644 examples/data-sources/netapp-ontap_storage_snapshot_policy/variables.tf create mode 100644 internal/provider/storage_snapshot_policies_data_source.go create mode 100644 internal/provider/storage_snapshot_policy_data_source.go diff --git a/docs/data-sources/storage_snapshot_policies_data_source.md b/docs/data-sources/storage_snapshot_policies_data_source.md new file mode 100644 index 00000000..8801cd1b --- /dev/null +++ b/docs/data-sources/storage_snapshot_policies_data_source.md @@ -0,0 +1,85 @@ +--- +page_title: "netapp-ontap_storage_snapshot_policies_data_source Data Source - terraform-provider-netapp-ontap" +subcategory: "storage" +description: |- + Retrieves Snapshot Policies information +--- + +# NetApp Ontap snapshot policies data source + +Snapshot Policies data source + +## Example Usage +```terraform +data "netapp-ontap_storage_snapshot_policies_data_source" "storage_snapshot_policies" { + # required to know which system to interface with + cx_profile_name = "cluster4" + filter = { + name = "ansible*" + } +} +``` + + + +## Schema + +### Required + +- `cx_profile_name` (String) Connection profile name + +### Optional + +- `filter` (Attributes) (see [below for nested schema](#nestedatt--filter)) + +### Read-Only + +- `storage_snapshot_policies` (Attributes List) (see [below for nested schema](#nestedatt--storage_snapshot_policies)) + + +### Nested Schema for `filter` + +Optional: + +- `name` (String) SnapshotPolicy name +- `svm_name` (String) SnapshotPolicy vserver name + + + +### Nested Schema for `storage_snapshot_policies` + +Required: + +- `cx_profile_name` (String) Connection profile name +- `name` (String) SnapshotPolicy name + +Read-Only: + +- `comment` (String) A comment associated with the Snapshot copy policy +- `copies` (Attributes List) Snapshot copy (see [below for nested schema](#nestedatt--storage_snapshot_policies--copies)) +- `enabled` (Boolean) Is the Snapshot copy policy enabled? +- `svm_name` (String) IPInterface vserver name +- `uuid` (String) SnapshotPolicy UUID + + +### Nested Schema for `storage_snapshot_policies.copies` + +Required: + +- `schedule` (Attributes) Schedule at which Snapshot copies are captured on the volume (see [below for nested schema](#nestedatt--storage_snapshot_policies--copies--schedule)) + +Read-Only: + +- `count` (Number) The number of Snapshot copies to maintain for this schedule +- `prefix` (String) The prefix to use while creating Snapshot copies at regular intervals +- `retention_period` (String) The retention period of Snapshot copies for this schedule +- `snapmirror_label` (String) Label for SnapMirror operations + + +### Nested Schema for `storage_snapshot_policies.copies.schedule` + +Read-Only: + +- `name` (String) Some common schedules already defined in the system are hourly, daily, weekly, at 15 minute intervals, and at 5 minute intervals. Snapshot copy policies with custom schedules can be referenced + + diff --git a/docs/data-sources/storage_snapshot_policy_data_source.md b/docs/data-sources/storage_snapshot_policy_data_source.md new file mode 100644 index 00000000..358e4872 --- /dev/null +++ b/docs/data-sources/storage_snapshot_policy_data_source.md @@ -0,0 +1,59 @@ +--- +page_title: "netapp-ontap_snapshot_policy_data_source Data Source - terraform-provider-netapp-ontap" +subcategory: "storage" +description: |- + Retrieves Snapshot Policy information +--- + +# NetApp Ontap snapshot policy data source + +Snapshot Policy data source + +## Example Usage +```terraform +data "netapp-ontap_storage_snapshot_policy_data_source" "storage_snapshot_policy" { + # required to know which system to interface with + cx_profile_name = "cluster4" + name = "ansible2" +} +``` + + + +## Schema + +### Required + +- `cx_profile_name` (String) Connection profile name +- `name` (String) SnapshotPolicy name + +### Read-Only + +- `comment` (String) A comment associated with the Snapshot copy policy +- `copies` (Attributes List) Snapshot copy (see [below for nested schema](#nestedatt--copies)) +- `enabled` (Boolean) Is the Snapshot copy policy enabled? +- `svm_name` (String) IPInterface vserver name +- `uuid` (String) SnapshotPolicy UUID + + +### Nested Schema for `copies` + +Required: + +- `schedule` (Attributes) Schedule at which Snapshot copies are captured on the volume (see [below for nested schema](#nestedatt--copies--schedule)) + +Read-Only: + +- `count` (Number) The number of Snapshot copies to maintain for this schedule +- `prefix` (String) The prefix to use while creating Snapshot copies at regular intervals +- `retention_period` (String) The retention period of Snapshot copies for this schedule +- `snapmirror_label` (String) Label for SnapMirror operations + + +### Nested Schema for `copies.schedule` + +Read-Only: + +- `name` (String) Some common schedules already defined in the system are hourly, daily, weekly, at 15 minute intervals, and at 5 minute intervals. Snapshot copy policies with custom schedules can be referenced + + diff --git a/examples/data-sources/netapp-ontap_storage_snapshot_policies/data-source.tf b/examples/data-sources/netapp-ontap_storage_snapshot_policies/data-source.tf new file mode 100644 index 00000000..dd0a24da --- /dev/null +++ b/examples/data-sources/netapp-ontap_storage_snapshot_policies/data-source.tf @@ -0,0 +1,7 @@ +data "netapp-ontap_storage_snapshot_policies_data_source" "storage_snapshot_policies" { + # required to know which system to interface with + cx_profile_name = "cluster4" + filter = { + name = "ansible*" + } +} diff --git a/examples/data-sources/netapp-ontap_storage_snapshot_policies/provider.tf b/examples/data-sources/netapp-ontap_storage_snapshot_policies/provider.tf new file mode 100644 index 00000000..9abcaafb --- /dev/null +++ b/examples/data-sources/netapp-ontap_storage_snapshot_policies/provider.tf @@ -0,0 +1,44 @@ +terraform { + required_providers { + netapp-ontap = { + source = "NetApp/netapp-ontap" + version = "0.0.1" + } + } +} + + +provider "netapp-ontap" { + # A connection profile defines how to interface with an ONTAP cluster or svm. + # At least one is required. + connection_profiles = [ + { + name = "cluster1" + hostname = "********219" + username = var.username + password = var.password + validate_certs = var.validate_certs + }, + { + name = "cluster2" + hostname = "********222" + username = var.username + password = var.password + validate_certs = var.validate_certs + }, + { + name = "cluster3" + hostname = "10.193.176.159" + username = var.username + password = var.password + validate_certs = var.validate_certs + }, + { + name = "cluster4" + hostname = "10.193.180.108" + username = var.username + password = var.password + validate_certs = var.validate_certs + } + ] +} diff --git a/examples/data-sources/netapp-ontap_storage_snapshot_policies/variables.tf b/examples/data-sources/netapp-ontap_storage_snapshot_policies/variables.tf new file mode 100644 index 00000000..b79f07ed --- /dev/null +++ b/examples/data-sources/netapp-ontap_storage_snapshot_policies/variables.tf @@ -0,0 +1,11 @@ +# Terraform will prompt for values, unless a tfvars file is present. +variable "username" { + type = string +} +variable "password" { + type = string + sensitive = true +} +variable "validate_certs" { + type = bool +} diff --git a/examples/data-sources/netapp-ontap_storage_snapshot_policy/data-source.tf b/examples/data-sources/netapp-ontap_storage_snapshot_policy/data-source.tf new file mode 100644 index 00000000..f040514f --- /dev/null +++ b/examples/data-sources/netapp-ontap_storage_snapshot_policy/data-source.tf @@ -0,0 +1,5 @@ +data "netapp-ontap_storage_snapshot_policy_data_source" "storage_snapshot_policy" { + # required to know which system to interface with + cx_profile_name = "cluster4" + name = "ansible2" +} diff --git a/examples/data-sources/netapp-ontap_storage_snapshot_policy/provider.tf b/examples/data-sources/netapp-ontap_storage_snapshot_policy/provider.tf new file mode 100644 index 00000000..9abcaafb --- /dev/null +++ b/examples/data-sources/netapp-ontap_storage_snapshot_policy/provider.tf @@ -0,0 +1,44 @@ +terraform { + required_providers { + netapp-ontap = { + source = "NetApp/netapp-ontap" + version = "0.0.1" + } + } +} + + +provider "netapp-ontap" { + # A connection profile defines how to interface with an ONTAP cluster or svm. + # At least one is required. + connection_profiles = [ + { + name = "cluster1" + hostname = "********219" + username = var.username + password = var.password + validate_certs = var.validate_certs + }, + { + name = "cluster2" + hostname = "********222" + username = var.username + password = var.password + validate_certs = var.validate_certs + }, + { + name = "cluster3" + hostname = "10.193.176.159" + username = var.username + password = var.password + validate_certs = var.validate_certs + }, + { + name = "cluster4" + hostname = "10.193.180.108" + username = var.username + password = var.password + validate_certs = var.validate_certs + } + ] +} diff --git a/examples/data-sources/netapp-ontap_storage_snapshot_policy/variables.tf b/examples/data-sources/netapp-ontap_storage_snapshot_policy/variables.tf new file mode 100644 index 00000000..b79f07ed --- /dev/null +++ b/examples/data-sources/netapp-ontap_storage_snapshot_policy/variables.tf @@ -0,0 +1,11 @@ +# Terraform will prompt for values, unless a tfvars file is present. +variable "username" { + type = string +} +variable "password" { + type = string + sensitive = true +} +variable "validate_certs" { + type = bool +} diff --git a/internal/interfaces/storage_snapshot_policy.go b/internal/interfaces/storage_snapshot_policy.go index f5572e42..df3fd8b1 100644 --- a/internal/interfaces/storage_snapshot_policy.go +++ b/internal/interfaces/storage_snapshot_policy.go @@ -48,6 +48,12 @@ type Schedule struct { Name string `mapstructure:"name"` } +// SnapshotPolicyGetDataFilterModel describes filter model +type SnapshotPolicyGetDataFilterModel struct { + Name string `mapstructure:"name"` + SVMName string `mapstructure:"svm.name"` +} + // GetSnapshotPolicy to get storage_snapshot_policy info func GetSnapshotPolicy(errorHandler *utils.ErrorHandler, r restclient.RestClient, id string) (*SnapshotPolicyGetDataModelONTAP, error) { api := "storage/snapshot-policies" @@ -71,11 +77,34 @@ func GetSnapshotPolicy(errorHandler *utils.ErrorHandler, r restclient.RestClient return &dataONTAP, nil } +// GetSnapshotPolicyByName to get storage_snapshot_policy info +func GetSnapshotPolicyByName(errorHandler *utils.ErrorHandler, r restclient.RestClient, name string) (*SnapshotPolicyGetDataModelONTAP, error) { + api := "storage/snapshot-policies" + query := r.NewQuery() + query.Set("name", name) + query.Fields([]string{"name", "svm.name", "copies", "scope", "enabled", "comment"}) + 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 storage_snapshot_policy info", fmt.Sprintf("error on GET %s: %s, statusCode %d", api, err, statusCode)) + } + + var dataONTAP SnapshotPolicyGetDataModelONTAP + 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 storage_snapshot_policy data source: %#v", dataONTAP)) + return &dataONTAP, nil +} + // GetSnapshotPolicies to get storage_snapshot_policy info for all resources matching a filter -func GetSnapshotPolicies(errorHandler *utils.ErrorHandler, r restclient.RestClient, filter *SnapshotPolicyGetDataModelONTAP) ([]SnapshotPolicyGetDataModelONTAP, error) { +func GetSnapshotPolicies(errorHandler *utils.ErrorHandler, r restclient.RestClient, filter *SnapshotPolicyGetDataFilterModel) ([]SnapshotPolicyGetDataModelONTAP, error) { api := "storage/snapshot-policies" query := r.NewQuery() - query.Fields([]string{"name", "svm.name", "scope", "enabled"}) + query.Fields([]string{"name", "svm.name", "copies", "scope", "enabled", "comment"}) if filter != nil { var filterMap map[string]interface{} if err := mapstructure.Decode(filter, &filterMap); err != nil { diff --git a/internal/interfaces/storage_snapshot_policy_test.go b/internal/interfaces/storage_snapshot_policy_test.go index e2973a50..f404fbbd 100644 --- a/internal/interfaces/storage_snapshot_policy_test.go +++ b/internal/interfaces/storage_snapshot_policy_test.go @@ -13,7 +13,7 @@ import ( "github.com/netapp/terraform-provider-netapp-ontap/internal/utils" ) -// Only requried parameters +// Only required parameters var basicSnapshotPolicyRecord = SnapshotPolicyGetDataModelONTAP{ Name: "string", UUID: "string", @@ -334,3 +334,163 @@ func TestDeleteSnapshotPolicy(t *testing.T) { }) } } + +func TestGetSnapshotPolicies(t *testing.T) { + errorHandler := utils.NewErrorHandler(context.Background(), &diag.Diagnostics{}) + var recordInterface map[string]any + err := mapstructure.Decode(basicSnapshotPolicyRecord, &recordInterface) + if err != nil { + panic(err) + } + + var secondRecordInterface map[string]any + err = mapstructure.Decode(twoSnapshotPolicyCopiesRecord, &secondRecordInterface) + if err != nil { + panic(err) + } + + var badRecordInterface map[string]any + err = mapstructure.Decode(badSnapshotPolicyRecord, &badRecordInterface) + if err != nil { + panic(err) + } + noRecords := restclient.RestResponse{NumRecords: 0, Records: []map[string]any{}} + oneRecord := restclient.RestResponse{NumRecords: 1, Records: []map[string]any{recordInterface}} + twoRecords := restclient.RestResponse{NumRecords: 2, Records: []map[string]any{recordInterface, secondRecordInterface}} + //genericError := errors.New("generic error for UT") + badRecordResponse := restclient.RestResponse{NumRecords: 1, Records: []map[string]any{badRecordInterface}} + + var OneRecord = []SnapshotPolicyGetDataModelONTAP{basicSnapshotPolicyRecord} + var TwoRecords = []SnapshotPolicyGetDataModelONTAP{basicSnapshotPolicyRecord, twoSnapshotPolicyCopiesRecord} + + responses := map[string][]restclient.MockResponse{ + "test_no_records_1": { + {ExpectedMethod: "GET", ExpectedURL: "storage/snapshot-policies", StatusCode: 200, Response: noRecords, Err: nil}, + }, + "test_one_record_1": { + + {ExpectedMethod: "GET", ExpectedURL: "storage/snapshot-policies", StatusCode: 200, Response: oneRecord, Err: nil}, + }, + "test_two_records_1": { + {ExpectedMethod: "GET", ExpectedURL: "storage/snapshot-policies", StatusCode: 200, Response: twoRecords, Err: nil}, + }, + "test_decode_error": { + {ExpectedMethod: "GET", ExpectedURL: "storage/snapshot-policies", StatusCode: 200, Response: badRecordResponse, Err: nil}, + }, + } + tests := []struct { + name string + responses []restclient.MockResponse + // args args + want []SnapshotPolicyGetDataModelONTAP + wantErr bool + }{ + {name: "test_no_records_1", responses: responses["test_no_records_1"], want: nil, wantErr: false}, + {name: "test_one_record_1", responses: responses["test_one_record_1"], want: OneRecord, wantErr: false}, + {name: "test_two_records_1", responses: responses["test_two_records_1"], want: TwoRecords, wantErr: false}, + {name: "test_decode_error", responses: responses["test_decode_error"], want: nil, wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r, err := restclient.NewMockedRestClient(tt.responses) + if err != nil { + panic(err) + } + got, err := GetSnapshotPolicies(errorHandler, *r, &SnapshotPolicyGetDataFilterModel{Name: ""}) + if err != nil { + fmt.Printf("err: %s\n", err) + } + if (err != nil) != tt.wantErr { + t.Errorf("GetSnapshotPoliciesDataSource() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetSnapshotPoliciesDataSource() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGetSnapshotPolicyByName(t *testing.T) { + errorHandler := utils.NewErrorHandler(context.Background(), &diag.Diagnostics{}) + + var basicRecordInterface map[string]any + err := mapstructure.Decode(basicSnapshotPolicyRecord, &basicRecordInterface) + if err != nil { + panic(err) + } + var twoCopiesRecordInterface map[string]any + err = mapstructure.Decode(twoSnapshotPolicyCopiesRecord, &twoCopiesRecordInterface) + if err != nil { + panic(err) + } + var notEnabledRecordInterface map[string]any + err = mapstructure.Decode(notEnabledSnapshotPolicyRecord, ¬EnabledRecordInterface) + if err != nil { + panic(err) + } + var badRecordInterface map[string]any + err = mapstructure.Decode(badSnapshotPolicyRecord, &badRecordInterface) + if err != nil { + panic(err) + } + noRecords := restclient.RestResponse{NumRecords: 0, Records: []map[string]any{}} + oneRecord := restclient.RestResponse{NumRecords: 1, Records: []map[string]any{basicRecordInterface}} + oneTwoCopiesRecord := restclient.RestResponse{NumRecords: 1, Records: []map[string]any{twoCopiesRecordInterface}} + oneNotEnabledRecord := restclient.RestResponse{NumRecords: 1, Records: []map[string]any{notEnabledRecordInterface}} + twoRecords := restclient.RestResponse{NumRecords: 2, Records: []map[string]any{basicRecordInterface, basicRecordInterface}} + genericError := errors.New("generic error for UT") + decodeError := restclient.RestResponse{NumRecords: 1, Records: []map[string]any{badRecordInterface}} + responses := map[string][]restclient.MockResponse{ + "test_no_records_1": { + {ExpectedMethod: "GET", ExpectedURL: "storage/snapshot-policies", StatusCode: 200, Response: noRecords, Err: nil}, + }, + "test_one_record_1": { + {ExpectedMethod: "GET", ExpectedURL: "storage/snapshot-policies", StatusCode: 200, Response: oneRecord, Err: nil}, + }, + "test_two_copies_record_1": { + {ExpectedMethod: "GET", ExpectedURL: "storage/snapshot-policies", StatusCode: 200, Response: oneTwoCopiesRecord, Err: nil}, + }, + "test_one_not_enabled_record_1": { + {ExpectedMethod: "GET", ExpectedURL: "storage/snapshot-policies", StatusCode: 200, Response: oneNotEnabledRecord, Err: nil}, + }, + "test_two_records_error": { + {ExpectedMethod: "GET", ExpectedURL: "storage/snapshot-policies", StatusCode: 200, Response: twoRecords, Err: genericError}, + }, + "test_error_1": { + {ExpectedMethod: "GET", ExpectedURL: "storage/snapshot-policies", StatusCode: 200, Response: decodeError, Err: nil}, + }, + } + tests := []struct { + name string + responses []restclient.MockResponse + want *SnapshotPolicyGetDataModelONTAP + wantErr bool + }{ + {name: "test_no_records_1", responses: responses["test_no_records_1"], want: nil, wantErr: true}, + {name: "test_one_record_1", responses: responses["test_one_record_1"], want: &basicSnapshotPolicyRecord, wantErr: false}, + {name: "test_two_copies_record_1", responses: responses["test_two_copies_record_1"], want: &twoSnapshotPolicyCopiesRecord, wantErr: false}, + {name: "test_one_not_enabled_record_1", responses: responses["test_one_not_enabled_record_1"], want: ¬EnabledSnapshotPolicyRecord, wantErr: false}, + {name: "test_two_records_error", responses: responses["test_two_records_error"], want: nil, wantErr: true}, + {name: "test_error_1", responses: responses["test_error_1"], want: nil, wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r, err := restclient.NewMockedRestClient(tt.responses) + if err != nil { + panic(err) + } + got, err := GetSnapshotPolicyByName(errorHandler, *r, "string") + if err != nil { + fmt.Printf("err: %s\n", err) + } + if (err != nil) != tt.wantErr { + t.Errorf("GetSnapshotPolicy() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetSnapshotPolicy() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 653691b6..21cdbeec 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -175,6 +175,8 @@ func (p *ONTAPProvider) DataSources(ctx context.Context) []func() datasource.Dat NewIPRouteDataSource, NewNameServicesDNSDataSource, NewProtocolsNfsServiceDataSource, + NewSnapshotPoliciesDataSource, + NewSnapshotPolicyDataSource, NewProtocolsNfsServicesDataSource, NewStorageAggregateDataSource, NewStorageAggregatesDataSource, diff --git a/internal/provider/storage_snapshot_policies_data_source.go b/internal/provider/storage_snapshot_policies_data_source.go new file mode 100644 index 00000000..61271cb8 --- /dev/null +++ b/internal/provider/storage_snapshot_policies_data_source.go @@ -0,0 +1,222 @@ +package provider + +import ( + "context" + "fmt" + + "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/utils" +) + +// Ensure provider defined types fully satisfy framework interfaces +var _ datasource.DataSource = &SnapshotPoliciesDataSource{} + +// NewSnapshotPoliciesDataSource is a helper function to simplify the provider implementation. +func NewSnapshotPoliciesDataSource() datasource.DataSource { + return &SnapshotPoliciesDataSource{ + config: resourceOrDataSourceConfig{ + name: "storage_snapshot_policies_data_source", + }, + } +} + +// SnapshotPoliciesDataSource defines the data source implementation. +type SnapshotPoliciesDataSource struct { + config resourceOrDataSourceConfig +} + +// SnapshotPoliciesDataSourceModel describes the data source data model. +type SnapshotPoliciesDataSourceModel struct { + CxProfileName types.String `tfsdk:"cx_profile_name"` + SnapshotPolicies []SnapshotPolicyDataSourceModel `tfsdk:"storage_snapshot_policies"` + Filter *SnapshotPolicyDataSourceFilterModel `tfsdk:"filter"` +} + +// Metadata returns the data source type name. +func (d *SnapshotPoliciesDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + d.config.name +} + +// Schema defines the schema for the data source. +func (d *SnapshotPoliciesDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: "SnapshotPolicies data source", + + Attributes: map[string]schema.Attribute{ + "cx_profile_name": schema.StringAttribute{ + MarkdownDescription: "Connection profile name", + Required: true, + }, + "filter": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + MarkdownDescription: "SnapshotPolicy name", + Optional: true, + }, + "svm_name": schema.StringAttribute{ + MarkdownDescription: "SnapshotPolicy svm name", + Optional: true, + }, + }, + Optional: true, + }, + "storage_snapshot_policies": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "cx_profile_name": schema.StringAttribute{ + MarkdownDescription: "Connection profile name", + Required: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "SnapshotPolicy name", + Required: true, + }, + "svm_name": schema.StringAttribute{ + MarkdownDescription: "IPInterface svm name", + Computed: true, + }, + "uuid": schema.StringAttribute{ + MarkdownDescription: "SnapshotPolicy UUID", + Computed: true, + }, + "copies": schema.ListNestedAttribute{ + MarkdownDescription: "Snapshot copy", + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "count": schema.Int64Attribute{ + MarkdownDescription: "The number of Snapshot copies to maintain for this schedule", + Computed: true, + }, + "schedule": schema.SingleNestedAttribute{ + MarkdownDescription: "Schedule at which Snapshot copies are captured on the volume", + Required: true, + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + MarkdownDescription: "Some common schedules already defined in the system are hourly, daily, weekly, at 15 minute intervals, and at 5 minute intervals. Snapshot copy policies with custom schedules can be referenced", + Computed: true, + }, + }, + }, + "retention_period": schema.StringAttribute{ + MarkdownDescription: "The retention period of Snapshot copies for this schedule", + Computed: true, + }, + "snapmirror_label": schema.StringAttribute{ + MarkdownDescription: "Label for SnapMirror operations", + Computed: true, + }, + "prefix": schema.StringAttribute{ + MarkdownDescription: "The prefix to use while creating Snapshot copies at regular intervals", + Computed: true, + }, + }, + }, + }, + "comment": schema.StringAttribute{ + MarkdownDescription: "A comment associated with the Snapshot copy policy", + Computed: true, + }, + "enabled": schema.BoolAttribute{ + MarkdownDescription: "Is the Snapshot copy policy enabled?", + Computed: true, + }, + }, + }, + Computed: true, + MarkdownDescription: "", + }, + }, + } +} + +// Configure adds the provider configured client to the data source. +func (d *SnapshotPoliciesDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + config, ok := req.ProviderData.(Config) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected Config, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + } + d.config.providerConfig = config +} + +// Read refreshes the Terraform state with the latest data. +func (d *SnapshotPoliciesDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data SnapshotPoliciesDataSourceModel + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + errorHandler := utils.NewErrorHandler(ctx, &resp.Diagnostics) + // we need to defer setting the client until we can read the connection profile name + client, err := getRestClient(errorHandler, d.config, data.CxProfileName) + if err != nil { + // error reporting done inside NewClient + return + } + + var filter *interfaces.SnapshotPolicyGetDataFilterModel = nil + if data.Filter != nil { + filter = &interfaces.SnapshotPolicyGetDataFilterModel{ + Name: data.Filter.Name.ValueString(), + SVMName: data.Filter.SVMName.ValueString(), + } + } + restInfo, err := interfaces.GetSnapshotPolicies(errorHandler, *client, filter) + if err != nil { + // error reporting done inside GetSnapshotPolicies + return + } + + data.SnapshotPolicies = make([]SnapshotPolicyDataSourceModel, len(restInfo)) + for index, record := range restInfo { + var copies = make([]CopyResourceModel, len(record.Copies)) + + for i, copiesRecord := range record.Copies { + copies[i] = CopyResourceModel{ + Count: types.Int64Value(copiesRecord.Count), + Schedule: ScheduleResourceModel{ + Name: types.StringValue(copiesRecord.Schedule.Name), + }, + SnapmirrorLabel: types.StringValue(copiesRecord.SnapmirrorLabel), + Prefix: types.StringValue(copiesRecord.Prefix), + } + + if copiesRecord.RetentionPeriod != "" { + copies[i].RetentionPeriod = types.StringValue(copiesRecord.RetentionPeriod) + } + } + + data.SnapshotPolicies[index] = SnapshotPolicyDataSourceModel{ + CxProfileName: types.String(data.CxProfileName), + Name: types.StringValue(record.Name), + SVMName: types.StringValue(record.SVM.Name), + UUID: types.StringValue(record.UUID), + Copies: copies, + Comment: types.StringValue(record.Comment), + Enabled: types.BoolValue(record.Enabled), + } + } + + // Write logs using the tflog package + // Documentation: https://terraform.io/plugin/log + tflog.Debug(ctx, fmt.Sprintf("read a data source: %#v", data)) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/internal/provider/storage_snapshot_policy_data_source.go b/internal/provider/storage_snapshot_policy_data_source.go new file mode 100644 index 00000000..a59b6f13 --- /dev/null +++ b/internal/provider/storage_snapshot_policy_data_source.go @@ -0,0 +1,190 @@ +package provider + +import ( + "context" + "fmt" + "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/utils" +) + +// Ensure provider defined types fully satisfy framework interfaces +var _ datasource.DataSource = &SnapshotPolicyDataSource{} + +// NewSnapshotPolicyDataSource is a helper function to simplify the provider implementation. +func NewSnapshotPolicyDataSource() datasource.DataSource { + return &SnapshotPolicyDataSource{ + config: resourceOrDataSourceConfig{ + name: "storage_snapshot_policy_data_source", + }, + } +} + +// SnapshotPolicyDataSource defines the data source implementation. +type SnapshotPolicyDataSource struct { + config resourceOrDataSourceConfig +} + +// SnapshotPolicyDataSourceModel describes the data source data model. +type SnapshotPolicyDataSourceModel struct { + CxProfileName types.String `tfsdk:"cx_profile_name"` + Name types.String `tfsdk:"name"` + SVMName types.String `tfsdk:"svm_name"` + UUID types.String `tfsdk:"uuid"` + Copies []CopyResourceModel `tfsdk:"copies"` + Comment types.String `tfsdk:"comment"` + Enabled types.Bool `tfsdk:"enabled"` +} + +// SnapshotPolicyDataSourceFilterModel describes the data source data model for queries. +type SnapshotPolicyDataSourceFilterModel struct { + Name types.String `tfsdk:"name"` + SVMName types.String `tfsdk:"svm_name"` +} + +// Metadata returns the data source type name. +func (d *SnapshotPolicyDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + d.config.name +} + +// Schema defines the schema for the data source. +func (d *SnapshotPolicyDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: "SnapshotPolicy data source", + + Attributes: map[string]schema.Attribute{ + "cx_profile_name": schema.StringAttribute{ + MarkdownDescription: "Connection profile name", + Required: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "SnapshotPolicy name", + Required: true, + }, + "svm_name": schema.StringAttribute{ + MarkdownDescription: "IPInterface svm name", + Computed: true, + }, + "uuid": schema.StringAttribute{ + MarkdownDescription: "SnapshotPolicy UUID", + Computed: true, + }, + "copies": schema.ListNestedAttribute{ + MarkdownDescription: "Snapshot copy", + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "count": schema.Int64Attribute{ + MarkdownDescription: "The number of Snapshot copies to maintain for this schedule", + Computed: true, + }, + "schedule": schema.SingleNestedAttribute{ + MarkdownDescription: "Schedule at which Snapshot copies are captured on the volume", + Required: true, + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + MarkdownDescription: "Some common schedules already defined in the system are hourly, daily, weekly, at 15 minute intervals, and at 5 minute intervals. Snapshot copy policies with custom schedules can be referenced", + Computed: true, + }, + }, + }, + "retention_period": schema.StringAttribute{ + MarkdownDescription: "The retention period of Snapshot copies for this schedule", + Computed: true, + }, + "snapmirror_label": schema.StringAttribute{ + MarkdownDescription: "Label for SnapMirror operations", + Computed: true, + }, + "prefix": schema.StringAttribute{ + MarkdownDescription: "The prefix to use while creating Snapshot copies at regular intervals", + Computed: true, + }, + }, + }, + }, + "comment": schema.StringAttribute{ + MarkdownDescription: "A comment associated with the Snapshot copy policy", + Computed: true, + }, + "enabled": schema.BoolAttribute{ + MarkdownDescription: "Is the Snapshot copy policy enabled?", + Computed: true, + }, + }, + } +} + +// Configure adds the provider configured client to the data source. +func (d *SnapshotPolicyDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + config, ok := req.ProviderData.(Config) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected Config, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + } + d.config.providerConfig = config +} + +// Read refreshes the Terraform state with the latest data. +func (d *SnapshotPolicyDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data SnapshotPolicyDataSourceModel + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + errorHandler := utils.NewErrorHandler(ctx, &resp.Diagnostics) + // we need to defer setting the client until we can read the connection profile name + client, err := getRestClient(errorHandler, d.config, data.CxProfileName) + if err != nil { + // error reporting done inside NewClient + return + } + + restInfo, err := interfaces.GetSnapshotPolicyByName(errorHandler, *client, data.Name.ValueString()) + if err != nil { + // error reporting done inside GetSnapshotPolicy + return + } + + data.Name = types.StringValue(restInfo.Name) + data.SVMName = types.StringValue(restInfo.SVM.Name) + data.UUID = types.StringValue(restInfo.UUID) + data.Copies = make([]CopyResourceModel, len(restInfo.Copies)) + for index, record := range restInfo.Copies { + data.Copies[index] = CopyResourceModel{ + Count: types.Int64Value(record.Count), + Schedule: ScheduleResourceModel{ + Name: types.StringValue(record.Schedule.Name), + }, + SnapmirrorLabel: types.StringValue(record.SnapmirrorLabel), + Prefix: types.StringValue(record.Prefix), + } + + if record.RetentionPeriod != "" { + data.Copies[index].RetentionPeriod = types.StringValue(record.RetentionPeriod) + } + } + data.Comment = types.StringValue(restInfo.Comment) + data.Enabled = types.BoolValue(restInfo.Enabled) + + // Write logs using the tflog package + // Documentation: https://terraform.io/plugin/log + tflog.Debug(ctx, fmt.Sprintf("read a data source: %#v", data)) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +}