diff --git a/CHANGELOG.md b/CHANGELOG.md index db458853..59d61058 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,12 @@ FEATURES: * **New Data Source:** `netapp_ontap_cluster_peers_data_source` ([#50](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/50)) * **New Data Source:** `netapp-ontap_protocols_cifs_local_user_data_source` ([#55](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/55)) * **New Data Source:** `netapp-ontap_protocols_cifs_local_users_data_source` ([#55](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/55)) -* **New Resource:** `netapp-ontap_protocols_cifs_local_group_resource` ([#53](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/53)) +* **New Data Source** `netapp-ontap_security_account_data_source` ([#22](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/22)) +* **New Data Source** `netapp-ontap_security_accounts_data_source` ([#22](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/22)) * **New Data Source:** `netapp-ontap_protocols_cifs_user_group_privilege_data_source` ([#57](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/57)) * **New Data Source:** `netapp-ontap_protocols_cifs_user_group_privileges_data_source` ([#57](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/57)) +* **New Resource:** `netapp-ontap_protocols_cifs_local_group_resource` ([#53](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/53)) + ENHANCEMENTS: * **netapp-ontap_protocols_nfs_export_policy_resource**: Add support for import ([#34](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/34)) diff --git a/docs/data-sources/security_account_data_source.md b/docs/data-sources/security_account_data_source.md new file mode 100644 index 00000000..135b0707 --- /dev/null +++ b/docs/data-sources/security_account_data_source.md @@ -0,0 +1,77 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "netapp-ontap_security_account_data_source Data Source - terraform-provider-netapp-ontap" +subcategory: "Security" +description: |- + Retrieves a ONTAP User +--- + +# Data Source Security_Account + +Retrieves a ONTAP User + +## Example Usage +```terraform +data "netapp-ontap_security_account_data_source" "security_accounts" { + # required to know which system to interface with + cx_profile_name = "cluster4" + owner = { + name = "ansibleSVM" + } + name = "vsadmin" +} +``` + + + +<!-- schema generated by tfplugindocs --> +## Schema + +### Required + +- `cx_profile_name` (String) Connection profile name +- `name` (String) SecurityAccount name + +### Optional + +- `owner` (Attributes) SecurityAccount owner (see [below for nested schema](#nestedatt--owner)) + +### Read-Only + +- `applications` (Attributes List) SecurityAccount applications (see [below for nested schema](#nestedatt--applications)) +- `comment` (String) SecurityAccount comment +- `id` (String) SecurityAccount id +- `locked` (Boolean) SecurityAccount locked +- `role` (Attributes) SecurityAccount role (see [below for nested schema](#nestedatt--role)) +- `scope` (String) SecurityAccount scope + +<a id="nestedatt--owner"></a> +### Nested Schema for `owner` + +Required: + +- `name` (String) SecurityAccount owner name + +Read-Only: + +- `uuid` (String) SecurityAccount owner uuid + + +<a id="nestedatt--applications"></a> +### Nested Schema for `applications` + +Read-Only: + +- `application` (String) SecurityAccount application +- `authentication_methods` (List of String) SecurityAccount authentication methods +- `second_authentication_method` (String) SecurityAccount second authentication method + + +<a id="nestedatt--role"></a> +### Nested Schema for `role` + +Read-Only: + +- `name` (String) SecurityAccount role name + + diff --git a/docs/data-sources/security_accounts_data_source.md b/docs/data-sources/security_accounts_data_source.md new file mode 100644 index 00000000..78892f84 --- /dev/null +++ b/docs/data-sources/security_accounts_data_source.md @@ -0,0 +1,94 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "netapp-ontap_security_accounts_data_source Data Source - terraform-provider-netapp-ontap" +subcategory: "Security" +description: |- + Retrieves the configuration of multiple user accounts +--- + +# Data Source Security Accounts + +Retrieves the configuration of multiple user accounts + +## Example Usage +```terraform +data "netapp-ontap_security_accounts_data_source" "security_accounts" { + # required to know which system to interface with + cx_profile_name = "cluster4" + filter = { + name = "vsadmin" + svm_name = "testImport" + } +} +``` + + +<!-- schema generated by tfplugindocs --> +## Schema + +### Required + +- `cx_profile_name` (String) Connection profile name + +### Optional + +- `filter` (Attributes) (see [below for nested schema](#nestedatt--filter)) + +### Read-Only + +- `security_accounts` (Attributes List) (see [below for nested schema](#nestedatt--security_accounts)) + +<a id="nestedatt--filter"></a> +### Nested Schema for `filter` + +Optional: + +- `name` (String) SecurityAccount name +- `svm_name` (String) SecurityAccount svm name (Owner name) + + +<a id="nestedatt--security_accounts"></a> +### Nested Schema for `security_accounts` + +Required: + +- `cx_profile_name` (String) Connection profile name +- `name` (String) SecurityAccount name + +Read-Only: + +- `applications` (Attributes List) SecurityAccount applications (see [below for nested schema](#nestedatt--security_accounts--applications)) +- `comment` (String) SecurityAccount comment +- `id` (String) SecurityAccount id +- `locked` (Boolean) SecurityAccount locked +- `owner` (Attributes) SecurityAccount owner (see [below for nested schema](#nestedatt--security_accounts--owner)) +- `role` (Attributes) SecurityAccount role (see [below for nested schema](#nestedatt--security_accounts--role)) +- `scope` (String) SecurityAccount scope + +<a id="nestedatt--security_accounts--applications"></a> +### Nested Schema for `security_accounts.applications` + +Read-Only: + +- `application` (String) SecurityAccount application +- `authentication_methods` (List of String) SecurityAccount authentication methods +- `second_authentication_method` (String) SecurityAccount second authentication method + + +<a id="nestedatt--security_accounts--owner"></a> +### Nested Schema for `security_accounts.owner` + +Read-Only: + +- `name` (String) SecurityAccount owner name +- `uuid` (String) SecurityAccount owner uuid + + +<a id="nestedatt--security_accounts--role"></a> +### Nested Schema for `security_accounts.role` + +Read-Only: + +- `name` (String) SecurityAccount role name + + diff --git a/examples/data-sources/netapp-ontap_security_account/data-source.tf b/examples/data-sources/netapp-ontap_security_account/data-source.tf new file mode 100644 index 00000000..511195a3 --- /dev/null +++ b/examples/data-sources/netapp-ontap_security_account/data-source.tf @@ -0,0 +1,21 @@ +data "netapp-ontap_security_account_data_source" "security_accounts" { + # required to know which system to interface with + cx_profile_name = "cluster4" + scope = "cluster" + name = "admin" +} + +data "netapp-ontap_security_account_data_source" "security_accounts2" { + # required to know which system to interface with + cx_profile_name = "cluster4" + name = "admin" +} + +data "netapp-ontap_security_account_data_source" "security_accounts3" { + # required to know which system to interface with + cx_profile_name = "cluster4" + owner = { + name = "carchi-test" + } + name = "vsadmin" +} \ No newline at end of file diff --git a/examples/data-sources/netapp-ontap_security_account/provider.tf b/examples/data-sources/netapp-ontap_security_account/provider.tf new file mode 120000 index 00000000..c6b7138f --- /dev/null +++ b/examples/data-sources/netapp-ontap_security_account/provider.tf @@ -0,0 +1 @@ +../../provider/provider.tf \ No newline at end of file diff --git a/examples/data-sources/netapp-ontap_security_account/variables.tf b/examples/data-sources/netapp-ontap_security_account/variables.tf new file mode 120000 index 00000000..395ce618 --- /dev/null +++ b/examples/data-sources/netapp-ontap_security_account/variables.tf @@ -0,0 +1 @@ +../../provider/variables.tf \ No newline at end of file diff --git a/examples/data-sources/netapp-ontap_security_accounts/data-source.tf b/examples/data-sources/netapp-ontap_security_accounts/data-source.tf new file mode 100644 index 00000000..7cf149e4 --- /dev/null +++ b/examples/data-sources/netapp-ontap_security_accounts/data-source.tf @@ -0,0 +1,33 @@ +data "netapp-ontap_security_accounts_data_source" "security_accounts" { + # required to know which system to interface with + cx_profile_name = "cluster4" + filter = { + name = "admin" + } +} + +data "netapp-ontap_security_accounts_data_source" "security_accounts2" { + # required to know which system to interface with + cx_profile_name = "cluster4" + filter = { + name = "a*" + } +} + +data "netapp-ontap_security_accounts_data_source" "security_accounts3" { + # required to know which system to interface with + cx_profile_name = "cluster4" + filter = { + name = "vsadmin" + } +} + +data "netapp-ontap_security_accounts_data_source" "security_accounts4" { + # required to know which system to interface with + cx_profile_name = "cluster4" + filter = { + name = "vsadmin" + svm_name = "carchi-test" + } +} + diff --git a/examples/data-sources/netapp-ontap_security_accounts/provider.tf b/examples/data-sources/netapp-ontap_security_accounts/provider.tf new file mode 120000 index 00000000..c6b7138f --- /dev/null +++ b/examples/data-sources/netapp-ontap_security_accounts/provider.tf @@ -0,0 +1 @@ +../../provider/provider.tf \ No newline at end of file diff --git a/examples/data-sources/netapp-ontap_security_accounts/variables.tf b/examples/data-sources/netapp-ontap_security_accounts/variables.tf new file mode 120000 index 00000000..395ce618 --- /dev/null +++ b/examples/data-sources/netapp-ontap_security_accounts/variables.tf @@ -0,0 +1 @@ +../../provider/variables.tf \ No newline at end of file diff --git a/internal/interfaces/security_account.go b/internal/interfaces/security_account.go new file mode 100644 index 00000000..669510f9 --- /dev/null +++ b/internal/interfaces/security_account.go @@ -0,0 +1,103 @@ +package interfaces + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/mitchellh/mapstructure" + "github.com/netapp/terraform-provider-netapp-ontap/internal/restclient" + "github.com/netapp/terraform-provider-netapp-ontap/internal/utils" +) + +// SecurityAccountGetDataModelONTAP describes the GET record data model using go types for mapping. +type SecurityAccountGetDataModelONTAP struct { + Name string `mapstructure:"name"` + Owner SecurityAccountOwner `mapstructure:"owner,omitempty"` + Locked bool `mapstructure:"locked,omitempty"` + Comment string `mapstructure:"comment,omitempty"` + Role SecurityAccountRole `mapstructure:"role,omitempty"` + Scope string `mapstructure:"scope,omitempty"` + Applications []SecurityAccountApplication `mapstructure:"applications,omitempty"` +} + +// SecurityAccountApplication describes the application data model using go types for mapping. +type SecurityAccountApplication struct { + Application string `mapstructure:"application,omitempty"` + SecondAuthenticationMethod string `mapstructure:"second_authentication_method,omitempty"` + AuthenticationMethods []string `mapstructure:"authentication_methods,omitempty"` +} + +// SecurityAccountRole describes the role data model using go types for mapping. +type SecurityAccountRole struct { + Name string `mapstructure:"name,omitempty"` +} + +// SecurityAccountOwner describes the owner data model using go types for mapping. +type SecurityAccountOwner struct { + Name string `mapstructure:"name,omitempty"` + UUID string `mapstructure:"uuid,omitempty"` +} + +// SecurityAccountDataSourceFilterModel describes the data source filter data model. +type SecurityAccountDataSourceFilterModel struct { + Name string `mapstructure:"name"` + Owner *SecurityAccountOwner `mapstructure:"owner,omitempty"` +} + +// GetSecurityAccountByName gets a security account by name. +func GetSecurityAccountByName(errorHandler *utils.ErrorHandler, r restclient.RestClient, name string, ownerName string) (*SecurityAccountGetDataModelONTAP, error) { + query := r.NewQuery() + query.Fields([]string{"name", "owner", "locked", "comment", "role", "scope", "applications"}) + query.Set("name", name) + var err error + var response map[string]interface{} + var statusCode int + if ownerName != "" { + statusCode, response, err = r.GetNilOrOneRecord("security/accounts/"+ownerName+"/"+name, query, nil) + } else { + query.Set("scope", "cluster") + statusCode, response, err = r.GetNilOrOneRecord("security/accounts/", query, nil) + } + if err != nil { + return nil, errorHandler.MakeAndReportError("Error occurred when getting security account", fmt.Sprintf("error on get security/account: %s", err)) + } + if response == nil { + return nil, errorHandler.MakeAndReportError("No Account found", fmt.Sprintf("No account with name: %s", name)) + } + var dataOntap *SecurityAccountGetDataModelONTAP + if error := mapstructure.Decode(response, &dataOntap); error != nil { + return nil, errorHandler.MakeAndReportError("Error occurred when decoding security account", fmt.Sprintf("error on decoding security/account: %s, statusCode: %d, response %+v", error, statusCode, response)) + } + tflog.Debug(errorHandler.Ctx, fmt.Sprintf("security account: %+v", dataOntap)) + return dataOntap, nil +} + +// GetSecurityAccounts gets all security accounts. +func GetSecurityAccounts(errorHandler *utils.ErrorHandler, r restclient.RestClient, svnName string, name string) ([]SecurityAccountGetDataModelONTAP, error) { + query := r.NewQuery() + query.Fields([]string{"name", "owner", "locked", "comment", "role", "scope", "applications"}) + if svnName != "" { + query.Set("owner.name", svnName) + } + if name != "" { + query.Set("name", name) + } + + tflog.Debug(errorHandler.Ctx, fmt.Sprintf("security account filter: %+v", query)) + statusCode, response, err := r.GetZeroOrMoreRecords("security/accounts", query, nil) + if err != nil { + return nil, errorHandler.MakeAndReportError("Error occurred when getting security accounts", fmt.Sprintf("error on get security/accounts: %s", err)) + } + if response == nil { + return nil, errorHandler.MakeAndReportError("No Accounts found", fmt.Sprintf("No accounts found")) + } + var dataOntap []SecurityAccountGetDataModelONTAP + for _, info := range response { + var dataOntapItem SecurityAccountGetDataModelONTAP + if error := mapstructure.Decode(info, &dataOntapItem); error != nil { + return nil, errorHandler.MakeAndReportError("Error occurred when decoding security account", fmt.Sprintf("error on decoding security/account: %s, statusCode: %d, response %+v", error, statusCode, response)) + } + dataOntap = append(dataOntap, dataOntapItem) + } + tflog.Debug(errorHandler.Ctx, fmt.Sprintf("security accounts: %+v", dataOntap)) + return dataOntap, nil +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 71bfa8e3..b7acdc6a 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -190,6 +190,8 @@ func (p *ONTAPProvider) DataSources(ctx context.Context) []func() datasource.Dat NewNameServicesDNSDataSource, NewNameServicesDNSsDataSource, NewProtocolsNfsServiceDataSource, + NewSecurityAccountDataSource, + NewSecurityAccountsDataSource, NewSnapmirrorDataSource, NewSnapmirrorsDataSource, NewSnapshotPoliciesDataSource, diff --git a/internal/provider/security_account_data_source.go b/internal/provider/security_account_data_source.go new file mode 100644 index 00000000..8f1c5a2c --- /dev/null +++ b/internal/provider/security_account_data_source.go @@ -0,0 +1,238 @@ +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 = &SecurityAccountDataSource{} + +// NewSecurityAccountDataSource is a helper function to simplify the provider implementation. +func NewSecurityAccountDataSource() datasource.DataSource { + return &SecurityAccountDataSource{ + config: resourceOrDataSourceConfig{ + name: "security_account_data_source", + }, + } +} + +// SecurityAccountDataSource defines the data source implementation. +type SecurityAccountDataSource struct { + config resourceOrDataSourceConfig +} + +// SecurityAccountDataSourceModel describes the data source data model. +type SecurityAccountDataSourceModel struct { + CxProfileName types.String `tfsdk:"cx_profile_name"` + Name types.String `tfsdk:"name"` + Owner *OwnerDataSourceModel `tfsdk:"owner"` + Locked types.Bool `tfsdk:"locked"` + Comment types.String `tfsdk:"comment"` + Role *RoleDataSourceModel `tfsdk:"role"` + Scope types.String `tfsdk:"scope"` + Applications []ApplicationsDataSourceModel `tfsdk:"applications"` + ID types.String `tfsdk:"id"` +} + +// ApplicationsDataSourceModel describes the data source data model. +type ApplicationsDataSourceModel struct { + Application types.String `tfsdk:"application"` + SecondAuthentiactionMethod types.String `tfsdk:"second_authentication_method"` + AuthenticationMethods *[]types.String `tfsdk:"authentication_methods"` +} + +// RoleDataSourceModel describes the data source data model. +type RoleDataSourceModel struct { + Name types.String `tfsdk:"name"` +} + +// OwnerDataSourceModel describes the data source data model. +type OwnerDataSourceModel struct { + Name types.String `tfsdk:"name"` + OwnerID types.String `tfsdk:"uuid"` +} + +// Metadata returns the data source type name. +func (d *SecurityAccountDataSource) 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 *SecurityAccountDataSource) 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: "SecurityAccount data source", + + Attributes: map[string]schema.Attribute{ + "cx_profile_name": schema.StringAttribute{ + MarkdownDescription: "Connection profile name", + Required: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "SecurityAccount name", + Required: true, + }, + "id": schema.StringAttribute{ + MarkdownDescription: "SecurityAccount id", + Computed: true, + }, + "owner": schema.SingleNestedAttribute{ + MarkdownDescription: "SecurityAccount owner", + Computed: true, + Optional: true, + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + MarkdownDescription: "SecurityAccount owner name", + Required: true, + }, + "uuid": schema.StringAttribute{ + MarkdownDescription: "SecurityAccount owner uuid", + Computed: true, + }, + }, + }, + "locked": schema.BoolAttribute{ + MarkdownDescription: "SecurityAccount locked", + Computed: true, + }, + "comment": schema.StringAttribute{ + MarkdownDescription: "SecurityAccount comment", + Computed: true, + }, + "role": schema.SingleNestedAttribute{ + MarkdownDescription: "SecurityAccount role", + Computed: true, + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + MarkdownDescription: "SecurityAccount role name", + Computed: true, + }, + }, + }, + "scope": schema.StringAttribute{ + MarkdownDescription: "SecurityAccount scope", + Computed: true, + Optional: true, + }, + "applications": schema.ListNestedAttribute{ + MarkdownDescription: "SecurityAccount applications", + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "application": schema.StringAttribute{ + MarkdownDescription: "SecurityAccount application", + Computed: true, + }, + "second_authentication_method": schema.StringAttribute{ + MarkdownDescription: "SecurityAccount second authentication method", + Computed: true, + }, + "authentication_methods": schema.ListAttribute{ + MarkdownDescription: "SecurityAccount authentication methods", + Computed: true, + ElementType: types.StringType, + }, + }, + }, + }, + }, + } +} + +// Configure adds the provider configured client to the data source. +func (d *SecurityAccountDataSource) 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 *SecurityAccountDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data SecurityAccountDataSourceModel + + // 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 svm *interfaces.SvmGetDataSourceModel + if data.Owner != nil { + svm, err = interfaces.GetSvmByName(errorHandler, *client, data.Owner.Name.ValueString()) + if err != nil { + // error reporting done inside GetSvmByName + return + } + } + var restInfo *interfaces.SecurityAccountGetDataModelONTAP + if svm == nil { + restInfo, err = interfaces.GetSecurityAccountByName(errorHandler, *client, data.Name.ValueString(), "") + if err != nil { + // error reporting done inside GetSecurityAccount + return + } + } else { + restInfo, err = interfaces.GetSecurityAccountByName(errorHandler, *client, data.Name.ValueString(), svm.UUID) + if err != nil { + // error reporting done inside GetSecurityAccount + return + } + } + + data.Name = types.StringValue(restInfo.Name) + // There is no ID in the REST response, so we use the name as ID + data.ID = types.StringValue(restInfo.Name) + data.Owner = &OwnerDataSourceModel{ + Name: types.StringValue(restInfo.Owner.Name), + OwnerID: types.StringValue(restInfo.Owner.UUID), + } + data.Locked = types.BoolValue(restInfo.Locked) + data.Comment = types.StringValue(restInfo.Comment) + data.Role = &RoleDataSourceModel{ + Name: types.StringValue(restInfo.Role.Name), + } + data.Scope = types.StringValue(restInfo.Scope) + data.Applications = make([]ApplicationsDataSourceModel, len(restInfo.Applications)) + for index, application := range restInfo.Applications { + data.Applications[index] = ApplicationsDataSourceModel{ + Application: types.StringValue(application.Application), + SecondAuthentiactionMethod: types.StringValue(application.SecondAuthenticationMethod), + } + var authenticationMethods []types.String + for _, authenticationMethod := range application.AuthenticationMethods { + authenticationMethods = append(authenticationMethods, types.StringValue(authenticationMethod)) + } + data.Applications[index].AuthenticationMethods = &authenticationMethods + } + + // 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/security_accounts_data_source.go b/internal/provider/security_accounts_data_source.go new file mode 100644 index 00000000..fc01f379 --- /dev/null +++ b/internal/provider/security_accounts_data_source.go @@ -0,0 +1,231 @@ +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 = &SecurityAccountsDataSource{} + +// NewSecurityAccountsDataSource is a helper function to simplify the provider implementation. +func NewSecurityAccountsDataSource() datasource.DataSource { + return &SecurityAccountsDataSource{ + config: resourceOrDataSourceConfig{ + name: "security_accounts_data_source", + }, + } +} + +// SecurityAccountsDataSource defines the data source implementation. +type SecurityAccountsDataSource struct { + config resourceOrDataSourceConfig +} + +// SecurityAccountsDataSourceModel describes the data source data model. +type SecurityAccountsDataSourceModel struct { + CxProfileName types.String `tfsdk:"cx_profile_name"` + SecurityAccounts []SecurityAccountDataSourceModel `tfsdk:"security_accounts"` + Filter *SecurityAccountDataSourceFilterModel `tfsdk:"filter"` +} + +// SecurityAccountDataSourceFilterModel describes the data source data model for queries. +type SecurityAccountDataSourceFilterModel struct { + Name types.String `tfsdk:"name"` + SVMName types.String `tfsdk:"svm_name"` +} + +// Metadata returns the data source type name. +func (d *SecurityAccountsDataSource) 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 *SecurityAccountsDataSource) 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: "SecurityAccounts 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: "SecurityAccount name", + Optional: true, + }, + "svm_name": schema.StringAttribute{ + MarkdownDescription: "SecurityAccount svm name (Owner name)", + Optional: true, + }, + }, + Optional: true, + }, + "security_accounts": 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: "SecurityAccount name", + Required: true, + }, + "id": schema.StringAttribute{ + MarkdownDescription: "SecurityAccount id", + Computed: true, + }, + "owner": schema.SingleNestedAttribute{ + MarkdownDescription: "SecurityAccount owner", + Computed: true, + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + MarkdownDescription: "SecurityAccount owner name", + Computed: true, + }, + "uuid": schema.StringAttribute{ + MarkdownDescription: "SecurityAccount owner uuid", + Computed: true, + }, + }, + }, + "locked": schema.BoolAttribute{ + MarkdownDescription: "SecurityAccount locked", + Computed: true, + }, + "comment": schema.StringAttribute{ + MarkdownDescription: "SecurityAccount comment", + Computed: true, + }, + "role": schema.SingleNestedAttribute{ + MarkdownDescription: "SecurityAccount role", + Computed: true, + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + MarkdownDescription: "SecurityAccount role name", + Computed: true, + }, + }, + }, + "scope": schema.StringAttribute{ + MarkdownDescription: "SecurityAccount scope", + Computed: true, + }, + "applications": schema.ListNestedAttribute{ + MarkdownDescription: "SecurityAccount applications", + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "application": schema.StringAttribute{ + MarkdownDescription: "SecurityAccount application", + Computed: true, + }, + "second_authentication_method": schema.StringAttribute{ + MarkdownDescription: "SecurityAccount second authentication method", + Computed: true, + }, + "authentication_methods": schema.ListAttribute{ + MarkdownDescription: "SecurityAccount authentication methods", + Computed: true, + ElementType: types.StringType, + }, + }, + }, + }, + }, + }, + Computed: true, + MarkdownDescription: "", + }, + }, + } +} + +// Configure adds the provider configured client to the data source. +func (d *SecurityAccountsDataSource) 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 *SecurityAccountsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data SecurityAccountsDataSourceModel + + // 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.GetSecurityAccounts(errorHandler, *client, data.Filter.SVMName.ValueString(), data.Filter.Name.ValueString()) + if err != nil { + // error reporting done inside GetSecurityAccounts + return + } + data.SecurityAccounts = make([]SecurityAccountDataSourceModel, len(restInfo)) + for index, record := range restInfo { + data.SecurityAccounts[index] = SecurityAccountDataSourceModel{ + CxProfileName: data.CxProfileName, + Name: types.StringValue(record.Name), + ID: types.StringValue(record.Name), + Owner: &OwnerDataSourceModel{ + Name: types.StringValue(record.Owner.Name), + OwnerID: types.StringValue(record.Owner.UUID), + }, + Locked: types.BoolValue(record.Locked), + Comment: types.StringValue(record.Comment), + Role: &RoleDataSourceModel{ + Name: types.StringValue(record.Role.Name), + }, + Scope: types.StringValue(record.Scope), + Applications: make([]ApplicationsDataSourceModel, len(record.Applications)), + } + for i, application := range record.Applications { + data.SecurityAccounts[index].Applications[i] = ApplicationsDataSourceModel{ + Application: types.StringValue(application.Application), + SecondAuthentiactionMethod: types.StringValue(application.SecondAuthenticationMethod), + } + var authenticationMethods []types.String + for _, authenticationMethod := range application.AuthenticationMethods { + authenticationMethods = append(authenticationMethods, types.StringValue(authenticationMethod)) + } + data.SecurityAccounts[index].Applications[i].AuthenticationMethods = &authenticationMethods + } + } + + // 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)...) +}