From 2dd831f9449e0d7c94d0bf29aebafbccc7f48ab8 Mon Sep 17 00:00:00 2001 From: Chris Archibald Date: Fri, 1 Dec 2023 10:24:51 -0800 Subject: [PATCH 01/12] Add account skeleton --- .../data-source.tf | 5 + .../netapp-ontap_security_account/provider.tf | 1 + .../terraform.tfvars | 1 + .../variables.tf | 1 + internal/interfaces/security_account.go | 123 ++++++++++++++ .../provider/security_account_data_source.go | 129 +++++++++++++++ .../provider/security_accounts_data_source.go | 156 ++++++++++++++++++ 7 files changed, 416 insertions(+) create mode 100644 examples/data-sources/netapp-ontap_security_account/data-source.tf create mode 120000 examples/data-sources/netapp-ontap_security_account/provider.tf create mode 120000 examples/data-sources/netapp-ontap_security_account/terraform.tfvars create mode 120000 examples/data-sources/netapp-ontap_security_account/variables.tf create mode 100644 internal/interfaces/security_account.go create mode 100644 internal/provider/security_account_data_source.go create mode 100644 internal/provider/security_accounts_data_source.go 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..e37834a5 --- /dev/null +++ b/examples/data-sources/netapp-ontap_security_account/data-source.tf @@ -0,0 +1,5 @@ +data "netapp-ontap_security_accounts_data_source" "security_accounts" { + # required to know which system to interface with + cx_profile_name = "cluster1" + # filter = {} +} 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/terraform.tfvars b/examples/data-sources/netapp-ontap_security_account/terraform.tfvars new file mode 120000 index 00000000..8d9d1c96 --- /dev/null +++ b/examples/data-sources/netapp-ontap_security_account/terraform.tfvars @@ -0,0 +1 @@ +../../provider/terraform.tfvars \ 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/internal/interfaces/security_account.go b/internal/interfaces/security_account.go new file mode 100644 index 00000000..a56d1508 --- /dev/null +++ b/internal/interfaces/security_account.go @@ -0,0 +1,123 @@ +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" +) + +// TODO: +// copy this file to match you data source (should match internal/interfaces/security_account.go) +// replace SecurityAccount with the name of the resource, following go conventions, eg IPInterface +// replace security_account with the name of the resource, for logging purposes, eg ip_interface +// replace api_url with API, eg ip/interfaces +// delete these 5 lines + +// SecurityAccountGetDataModelONTAP describes the GET record data model using go types for mapping. +type SecurityAccountGetDataModelONTAP struct { + Name string `mapstructure:"name"` + UUID string `mapstructure:"uuid"` +} + +// SecurityAccountResourceBodyDataModelONTAP describes the body data model using go types for mapping. +type SecurityAccountResourceBodyDataModelONTAP struct { + Name string `mapstructure:"name"` + SVM svm `mapstructure:"svm"` +} + +// GetSecurityAccount to get security_account info +func GetSecurityAccount(errorHandler *utils.ErrorHandler, r restclient.RestClient, name string, svmName string) (*SecurityAccountGetDataModelONTAP, error) { + api := "api_url" + query := r.NewQuery() + query.Set("name", name) + if svmName == "" { + query.Set("scope", "cluster") + } else { + query.Set("svm.name", svmName) + query.Set("scope", "svm") + } + query.Fields([]string{"name", "svm.name", "ip", "scope"}) + 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 security_account info", fmt.Sprintf("error on GET %s: %s, statusCode %d", api, err, statusCode)) + } + + var dataONTAP SecurityAccountGetDataModelONTAP + 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 security_account data source: %#v", dataONTAP)) + return &dataONTAP, nil +} + +// GetSecurityAccounts to get security_account info for all resources matching a filter +func GetSecurityAccounts(errorHandler *utils.ErrorHandler, r restclient.RestClient, filter *SecurityAccountGetDataModelONTAP) ([]SecurityAccountGetDataModelONTAP, error) { + api := "api_url" + query := r.NewQuery() + query.Fields([]string{"name", "svm.name", "scope"}) + if filter != nil { + var filterMap map[string]interface{} + if err := mapstructure.Decode(filter, &filterMap); err != nil { + return nil, errorHandler.MakeAndReportError("error encoding security_account filter info", fmt.Sprintf("error on filter %#v: %s", filter, err)) + } + query.SetValues(filterMap) + } + statusCode, response, err := r.GetZeroOrMoreRecords(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 security_account info", fmt.Sprintf("error on GET %s: %s, statusCode %d", api, err, statusCode)) + } + + var dataONTAP []SecurityAccountGetDataModelONTAP + for _, info := range response { + var record SecurityAccountGetDataModelONTAP + if err := mapstructure.Decode(info, &record); err != nil { + return nil, errorHandler.MakeAndReportError(fmt.Sprintf("failed to decode response from GET %s", api), + fmt.Sprintf("error: %s, statusCode %d, info %#v", err, statusCode, info)) + } + dataONTAP = append(dataONTAP, record) + } + tflog.Debug(errorHandler.Ctx, fmt.Sprintf("Read security_account data source: %#v", dataONTAP)) + return dataONTAP, nil +} + +// CreateSecurityAccount to create security_account +func CreateSecurityAccount(errorHandler *utils.ErrorHandler, r restclient.RestClient, body SecurityAccountResourceBodyDataModelONTAP) (*SecurityAccountGetDataModelONTAP, error) { + api := "api_url" + var bodyMap map[string]interface{} + if err := mapstructure.Decode(body, &bodyMap); err != nil { + return nil, errorHandler.MakeAndReportError("error encoding security_account body", fmt.Sprintf("error on encoding %s body: %s, body: %#v", api, err, body)) + } + query := r.NewQuery() + query.Add("return_records", "true") + statusCode, response, err := r.CallCreateMethod(api, query, bodyMap) + if err != nil { + return nil, errorHandler.MakeAndReportError("error creating security_account", fmt.Sprintf("error on POST %s: %s, statusCode %d", api, err, statusCode)) + } + + var dataONTAP SecurityAccountGetDataModelONTAP + if err := mapstructure.Decode(response.Records[0], &dataONTAP); err != nil { + return nil, errorHandler.MakeAndReportError("error decoding security_account info", fmt.Sprintf("error on decode storage/security_accounts info: %s, statusCode %d, response %#v", err, statusCode, response)) + } + tflog.Debug(errorHandler.Ctx, fmt.Sprintf("Create security_account source - udata: %#v", dataONTAP)) + return &dataONTAP, nil +} + +// DeleteSecurityAccount to delete security_account +func DeleteSecurityAccount(errorHandler *utils.ErrorHandler, r restclient.RestClient, uuid string) error { + api := "api_url" + statusCode, _, err := r.CallDeleteMethod(api+"/"+uuid, nil, nil) + if err != nil { + return errorHandler.MakeAndReportError("error deleting security_account", fmt.Sprintf("error on DELETE %s: %s, statusCode %d", api, err, statusCode)) + } + return nil +} diff --git a/internal/provider/security_account_data_source.go b/internal/provider/security_account_data_source.go new file mode 100644 index 00000000..fa198188 --- /dev/null +++ b/internal/provider/security_account_data_source.go @@ -0,0 +1,129 @@ +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" +) + +// TODO: +// copy this file to match you data source (should match internal/provider/security_account_data_source.go) +// replace SecurityAccount with the name of the resource, following go conventions, eg IPInterface +// replace security_account with the name of the resource, for logging purposes, eg ip_interface +// make sure to create internal/interfaces/security_account.go too) +// delete these 5 lines + +// 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"` + SVMName types.String `tfsdk:"svm_name"` +} + +// 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 *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, + }, + "svm_name": schema.StringAttribute{ + MarkdownDescription: "IPInterface svm name", + Optional: true, + }, + }, + } +} + +// 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 + } + + restInfo, err := interfaces.GetSecurityAccount(errorHandler, *client, data.Name.ValueString(), data.SVMName.ValueString()) + if err != nil { + // error reporting done inside GetSecurityAccount + return + } + + data.Name = types.StringValue(restInfo.Name) + + // 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..da296f06 --- /dev/null +++ b/internal/provider/security_accounts_data_source.go @@ -0,0 +1,156 @@ +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" +) + +// TODO: +// copy this file to match you data source (should match internal/provider/security_account_data_source.go) +// replace SecurityAccounts with the name of the resource, following go conventions, eg IPInterfaces +// replace security_accounts with the name of the resource, for logging purposes, eg ip_interfaces +// make sure to create internal/interfaces/security_account.go too) +// delete these 5 lines + +// 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"` +} + +// 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", + 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, + }, + }, + }, + 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 + } + + var filter *interfaces.SecurityAccountGetDataModelONTAP = nil + if data.Filter != nil { + filter = &interfaces.SecurityAccountGetDataModelONTAP{ + Name: data.Filter.Name.ValueString(), + } + } + restInfo, err := interfaces.GetSecurityAccounts(errorHandler, *client, filter) + 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: types.String(data.CxProfileName), + Name: types.StringValue(record.Name), + } + } + + // 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)...) +} From b252be9ac46c2653ca89ca80b028dbcb593fe090 Mon Sep 17 00:00:00 2001 From: Chris Archibald Date: Fri, 1 Dec 2023 12:40:41 -0800 Subject: [PATCH 02/12] woring single security account with out application --- .../data-source.tf | 9 +- .../terraform.tfvars | 1 - internal/interfaces/security_account.go | 124 +++++------------- internal/provider/provider.go | 2 + .../provider/security_account_data_source.go | 113 ++++++++++++++-- .../provider/security_accounts_data_source.go | 26 +--- 6 files changed, 146 insertions(+), 129 deletions(-) delete mode 120000 examples/data-sources/netapp-ontap_security_account/terraform.tfvars diff --git a/examples/data-sources/netapp-ontap_security_account/data-source.tf b/examples/data-sources/netapp-ontap_security_account/data-source.tf index e37834a5..8969d758 100644 --- a/examples/data-sources/netapp-ontap_security_account/data-source.tf +++ b/examples/data-sources/netapp-ontap_security_account/data-source.tf @@ -1,5 +1,8 @@ -data "netapp-ontap_security_accounts_data_source" "security_accounts" { +data "netapp-ontap_security_account_data_source" "security_accounts" { # required to know which system to interface with - cx_profile_name = "cluster1" - # filter = {} + cx_profile_name = "cluster4" + owner = { + name = "ansibleSVM" + } + name = "vsadmin" } diff --git a/examples/data-sources/netapp-ontap_security_account/terraform.tfvars b/examples/data-sources/netapp-ontap_security_account/terraform.tfvars deleted file mode 120000 index 8d9d1c96..00000000 --- a/examples/data-sources/netapp-ontap_security_account/terraform.tfvars +++ /dev/null @@ -1 +0,0 @@ -../../provider/terraform.tfvars \ No newline at end of file diff --git a/internal/interfaces/security_account.go b/internal/interfaces/security_account.go index a56d1508..2adbd263 100644 --- a/internal/interfaces/security_account.go +++ b/internal/interfaces/security_account.go @@ -2,7 +2,6 @@ package interfaces import ( "fmt" - "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/mitchellh/mapstructure" "github.com/netapp/terraform-provider-netapp-ontap/internal/restclient" @@ -18,106 +17,53 @@ import ( // SecurityAccountGetDataModelONTAP describes the GET record data model using go types for mapping. type SecurityAccountGetDataModelONTAP struct { - Name string `mapstructure:"name"` - UUID string `mapstructure:"uuid"` + 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"` } -// SecurityAccountResourceBodyDataModelONTAP describes the body data model using go types for mapping. -type SecurityAccountResourceBodyDataModelONTAP struct { - Name string `mapstructure:"name"` - SVM svm `mapstructure:"svm"` +// 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"` } -// GetSecurityAccount to get security_account info -func GetSecurityAccount(errorHandler *utils.ErrorHandler, r restclient.RestClient, name string, svmName string) (*SecurityAccountGetDataModelONTAP, error) { - api := "api_url" - query := r.NewQuery() - query.Set("name", name) - if svmName == "" { - query.Set("scope", "cluster") - } else { - query.Set("svm.name", svmName) - query.Set("scope", "svm") - } - query.Fields([]string{"name", "svm.name", "ip", "scope"}) - 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 security_account info", fmt.Sprintf("error on GET %s: %s, statusCode %d", api, err, statusCode)) - } - - var dataONTAP SecurityAccountGetDataModelONTAP - 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 security_account data source: %#v", dataONTAP)) - return &dataONTAP, nil +// SecurityAccountRole describes the role data model using go types for mapping. +type SecurityAccountRole struct { + Name string `mapstructure:"name,omitempty"` } -// GetSecurityAccounts to get security_account info for all resources matching a filter -func GetSecurityAccounts(errorHandler *utils.ErrorHandler, r restclient.RestClient, filter *SecurityAccountGetDataModelONTAP) ([]SecurityAccountGetDataModelONTAP, error) { - api := "api_url" - query := r.NewQuery() - query.Fields([]string{"name", "svm.name", "scope"}) - if filter != nil { - var filterMap map[string]interface{} - if err := mapstructure.Decode(filter, &filterMap); err != nil { - return nil, errorHandler.MakeAndReportError("error encoding security_account filter info", fmt.Sprintf("error on filter %#v: %s", filter, err)) - } - query.SetValues(filterMap) - } - statusCode, response, err := r.GetZeroOrMoreRecords(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 security_account info", fmt.Sprintf("error on GET %s: %s, statusCode %d", api, err, statusCode)) - } +// SecurityAccountOwner describes the owner data model using go types for mapping. +type SecurityAccountOwner struct { + Name string `mapstructure:"name"` + UUID string `mapstructure:"uuid,omitempty"` +} - var dataONTAP []SecurityAccountGetDataModelONTAP - for _, info := range response { - var record SecurityAccountGetDataModelONTAP - if err := mapstructure.Decode(info, &record); err != nil { - return nil, errorHandler.MakeAndReportError(fmt.Sprintf("failed to decode response from GET %s", api), - fmt.Sprintf("error: %s, statusCode %d, info %#v", err, statusCode, info)) - } - dataONTAP = append(dataONTAP, record) - } - tflog.Debug(errorHandler.Ctx, fmt.Sprintf("Read security_account data source: %#v", dataONTAP)) - return dataONTAP, nil +// SecurityAccountResourceBodyDataModelONTAP describes the body data model using go types for mapping. +type SecurityAccountResourceBodyDataModelONTAP struct { + Name string `mapstructure:"name"` + SVM svm `mapstructure:"svm"` } -// CreateSecurityAccount to create security_account -func CreateSecurityAccount(errorHandler *utils.ErrorHandler, r restclient.RestClient, body SecurityAccountResourceBodyDataModelONTAP) (*SecurityAccountGetDataModelONTAP, error) { - api := "api_url" - var bodyMap map[string]interface{} - if err := mapstructure.Decode(body, &bodyMap); err != nil { - return nil, errorHandler.MakeAndReportError("error encoding security_account body", fmt.Sprintf("error on encoding %s body: %s, body: %#v", api, err, body)) - } +func GetSecurityAccountByName(errorHandler *utils.ErrorHandler, r restclient.RestClient, name string, ownerName string) (*SecurityAccountGetDataModelONTAP, error) { query := r.NewQuery() - query.Add("return_records", "true") - statusCode, response, err := r.CallCreateMethod(api, query, bodyMap) + query.Fields([]string{"name", "owner", "locked", "comment", "role", "scope", "applications"}) + statusCode, response, err := r.GetNilOrOneRecord("security/accounts/"+ownerName+"/"+name, query, nil) if err != nil { - return nil, errorHandler.MakeAndReportError("error creating security_account", fmt.Sprintf("error on POST %s: %s, statusCode %d", api, err, statusCode)) + return nil, errorHandler.MakeAndReportError("Error occurred when getting security account", fmt.Sprintf("error on get security/account: %s", err)) } - - var dataONTAP SecurityAccountGetDataModelONTAP - if err := mapstructure.Decode(response.Records[0], &dataONTAP); err != nil { - return nil, errorHandler.MakeAndReportError("error decoding security_account info", fmt.Sprintf("error on decode storage/security_accounts info: %s, statusCode %d, response %#v", err, statusCode, response)) + if response == nil { + return nil, errorHandler.MakeAndReportError("No Account found", fmt.Sprintf("No account with name: %s", name)) } - tflog.Debug(errorHandler.Ctx, fmt.Sprintf("Create security_account source - udata: %#v", dataONTAP)) - return &dataONTAP, nil -} - -// DeleteSecurityAccount to delete security_account -func DeleteSecurityAccount(errorHandler *utils.ErrorHandler, r restclient.RestClient, uuid string) error { - api := "api_url" - statusCode, _, err := r.CallDeleteMethod(api+"/"+uuid, nil, nil) - if err != nil { - return errorHandler.MakeAndReportError("error deleting security_account", fmt.Sprintf("error on DELETE %s: %s, statusCode %d", api, err, statusCode)) + 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)) } - return nil + tflog.Debug(errorHandler.Ctx, fmt.Sprintf("security account: %+v", dataOntap)) + return dataOntap, nil } diff --git a/internal/provider/provider.go b/internal/provider/provider.go index e50ddf98..bc06ea3b 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -181,6 +181,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 index fa198188..38f9d26a 100644 --- a/internal/provider/security_account_data_source.go +++ b/internal/provider/security_account_data_source.go @@ -3,7 +3,6 @@ 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" @@ -12,13 +11,6 @@ import ( "github.com/netapp/terraform-provider-netapp-ontap/internal/utils" ) -// TODO: -// copy this file to match you data source (should match internal/provider/security_account_data_source.go) -// replace SecurityAccount with the name of the resource, following go conventions, eg IPInterface -// replace security_account with the name of the resource, for logging purposes, eg ip_interface -// make sure to create internal/interfaces/security_account.go too) -// delete these 5 lines - // Ensure provider defined types fully satisfy framework interfaces var _ datasource.DataSource = &SecurityAccountDataSource{} @@ -38,9 +30,32 @@ type SecurityAccountDataSource struct { // SecurityAccountDataSourceModel describes the data source data model. type SecurityAccountDataSourceModel struct { - CxProfileName types.String `tfsdk:"cx_profile_name"` - Name types.String `tfsdk:"name"` - SVMName types.String `tfsdk:"svm_name"` + 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"` +} + +// 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"` } // SecurityAccountDataSourceFilterModel describes the data source data model for queries. @@ -69,9 +84,63 @@ func (d *SecurityAccountDataSource) Schema(ctx context.Context, req datasource.S MarkdownDescription: "SecurityAccount name", Required: true, }, - "svm_name": schema.StringAttribute{ - MarkdownDescription: "IPInterface svm name", + "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, + }, + "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, + }, + }, + }, }, }, } @@ -112,13 +181,29 @@ func (d *SecurityAccountDataSource) Read(ctx context.Context, req datasource.Rea return } - restInfo, err := interfaces.GetSecurityAccount(errorHandler, *client, data.Name.ValueString(), data.SVMName.ValueString()) + svm, err := interfaces.GetSvmByName(errorHandler, *client, data.Owner.Name.ValueString()) + if err != nil { + // error reporting done inside GetSvmByName + return + } + + 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) + 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) // Write logs using the tflog package // Documentation: https://terraform.io/plugin/log diff --git a/internal/provider/security_accounts_data_source.go b/internal/provider/security_accounts_data_source.go index da296f06..4566739d 100644 --- a/internal/provider/security_accounts_data_source.go +++ b/internal/provider/security_accounts_data_source.go @@ -8,7 +8,6 @@ import ( "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" ) @@ -38,9 +37,9 @@ type SecurityAccountsDataSource struct { // 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"` + CxProfileName types.String `tfsdk:"cx_profile_name"` + SecurityAccounts []SecurityAccountDataSourceModel `tfsdk:"security_accounts"` + Filter *SecurityAccountDataSourceFilterModel `tfsdk:"filter"` } // Metadata returns the data source type name. @@ -126,27 +125,10 @@ func (d *SecurityAccountsDataSource) Read(ctx context.Context, req datasource.Re // error reporting done inside NewClient return } - - var filter *interfaces.SecurityAccountGetDataModelONTAP = nil - if data.Filter != nil { - filter = &interfaces.SecurityAccountGetDataModelONTAP{ - Name: data.Filter.Name.ValueString(), - } - } - restInfo, err := interfaces.GetSecurityAccounts(errorHandler, *client, filter) - if err != nil { - // error reporting done inside GetSecurityAccounts + if client == nil { return } - data.SecurityAccounts = make([]SecurityAccountDataSourceModel, len(restInfo)) - for index, record := range restInfo { - data.SecurityAccounts[index] = SecurityAccountDataSourceModel{ - CxProfileName: types.String(data.CxProfileName), - Name: types.StringValue(record.Name), - } - } - // Write logs using the tflog package // Documentation: https://terraform.io/plugin/log tflog.Debug(ctx, fmt.Sprintf("read a data source: %#v", data)) From fe3706c058611ec62ae0d127ff667b2786570d87 Mon Sep 17 00:00:00 2001 From: Chris Archibald Date: Fri, 1 Dec 2023 12:58:49 -0800 Subject: [PATCH 03/12] getting application to work --- .../provider/security_account_data_source.go | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/internal/provider/security_account_data_source.go b/internal/provider/security_account_data_source.go index 38f9d26a..266e4375 100644 --- a/internal/provider/security_account_data_source.go +++ b/internal/provider/security_account_data_source.go @@ -30,14 +30,14 @@ type SecurityAccountDataSource struct { // 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"` + 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"` } // ApplicationsDataSourceModel describes the data source data model. @@ -204,6 +204,18 @@ func (d *SecurityAccountDataSource) Read(ctx context.Context, req datasource.Rea 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 From ebfb4845dc102828101541bbccc85a3e32d55329 Mon Sep 17 00:00:00 2001 From: Chris Archibald Date: Fri, 1 Dec 2023 14:14:47 -0800 Subject: [PATCH 04/12] save --- .../data-source.tf | 7 ++ .../provider.tf | 1 + .../variables.tf | 1 + internal/interfaces/security_account.go | 38 +++++- .../provider/security_account_data_source.go | 6 - .../provider/security_accounts_data_source.go | 118 +++++++++++++++++- 6 files changed, 158 insertions(+), 13 deletions(-) create mode 100644 examples/data-sources/netapp-ontap_security_accounts/data-source.tf create mode 120000 examples/data-sources/netapp-ontap_security_accounts/provider.tf create mode 120000 examples/data-sources/netapp-ontap_security_accounts/variables.tf 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..27f68572 --- /dev/null +++ b/examples/data-sources/netapp-ontap_security_accounts/data-source.tf @@ -0,0 +1,7 @@ +data "netapp-ontap_security_accounts_data_source" "security_accounts" { + # required to know which system to interface with + cx_profile_name = "cluster4" + filter = { + name = "vsadmin" + } +} 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 index 2adbd263..ab51f319 100644 --- a/internal/interfaces/security_account.go +++ b/internal/interfaces/security_account.go @@ -44,10 +44,10 @@ type SecurityAccountOwner struct { UUID string `mapstructure:"uuid,omitempty"` } -// SecurityAccountResourceBodyDataModelONTAP describes the body data model using go types for mapping. -type SecurityAccountResourceBodyDataModelONTAP struct { - Name string `mapstructure:"name"` - SVM svm `mapstructure:"svm"` +// SecurityAccountDataSourceFilterModel describes the data source filter data model. +type SecurityAccountDataSourceFilterModel struct { + Name string `tfsdk:"name"` + Owner SecurityAccountOwner `tfsdk:"owner,omitempty"` } func GetSecurityAccountByName(errorHandler *utils.ErrorHandler, r restclient.RestClient, name string, ownerName string) (*SecurityAccountGetDataModelONTAP, error) { @@ -67,3 +67,33 @@ func GetSecurityAccountByName(errorHandler *utils.ErrorHandler, r restclient.Res tflog.Debug(errorHandler.Ctx, fmt.Sprintf("security account: %+v", dataOntap)) return dataOntap, nil } + +func GetSecurityAccounts(errorHandler *utils.ErrorHandler, r restclient.RestClient, filter *SecurityAccountDataSourceFilterModel) ([]SecurityAccountGetDataModelONTAP, error) { + query := r.NewQuery() + query.Fields([]string{"name", "owner", "locked", "comment", "role", "scope", "applications"}) + if filter != nil { + var filterMap map[string]interface{} + if error := mapstructure.Decode(filter, &filterMap); error != nil { + return nil, errorHandler.MakeAndReportError("Error occurred when decoding security account filter", fmt.Sprintf("error on decoding security/account filter: %s", error)) + } + query.SetValues(filterMap) + } + 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/security_account_data_source.go b/internal/provider/security_account_data_source.go index 266e4375..807ab4f4 100644 --- a/internal/provider/security_account_data_source.go +++ b/internal/provider/security_account_data_source.go @@ -58,12 +58,6 @@ type OwnerDataSourceModel struct { OwnerID types.String `tfsdk:"uuid"` } -// 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 *SecurityAccountDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_" + d.config.name diff --git a/internal/provider/security_accounts_data_source.go b/internal/provider/security_accounts_data_source.go index 4566739d..e8e82ad9 100644 --- a/internal/provider/security_accounts_data_source.go +++ b/internal/provider/security_accounts_data_source.go @@ -3,11 +3,11 @@ 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" ) @@ -42,6 +42,12 @@ type SecurityAccountsDataSourceModel struct { 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 @@ -65,7 +71,7 @@ func (d *SecurityAccountsDataSource) Schema(ctx context.Context, req datasource. Optional: true, }, "svm_name": schema.StringAttribute{ - MarkdownDescription: "SecurityAccount svm name", + MarkdownDescription: "SecurityAccount svm name (Owner name)", Optional: true, }, }, @@ -82,6 +88,63 @@ func (d *SecurityAccountsDataSource) Schema(ctx context.Context, req datasource. MarkdownDescription: "SecurityAccount name", Required: 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, @@ -125,9 +188,58 @@ func (d *SecurityAccountsDataSource) Read(ctx context.Context, req datasource.Re // error reporting done inside NewClient return } - if client == nil { + + var filter *interfaces.SecurityAccountDataSourceFilterModel = nil + if data.Filter != nil { + if data.Filter.SVMName.IsNull() { + filter = &interfaces.SecurityAccountDataSourceFilterModel{ + Name: data.Filter.Name.ValueString(), + } + } else { + filter = &interfaces.SecurityAccountDataSourceFilterModel{ + Name: data.Filter.Name.ValueString(), + Owner: interfaces.SecurityAccountOwner{ + Name: data.Filter.SVMName.ValueString(), + }, + } + } + + } + tflog.Debug(errorHandler.Ctx, fmt.Sprintf("security account filter: %+v", filter)) + restInfo, err := interfaces.GetSecurityAccounts(errorHandler, *client, filter) + 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), + 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 From a314b501e1131c9a2ef1d2df933898122acc31f5 Mon Sep 17 00:00:00 2001 From: Chris Archibald Date: Mon, 4 Dec 2023 08:22:52 -0800 Subject: [PATCH 05/12] updates --- .../netapp-ontap_security_accounts/data-source.tf | 1 + internal/interfaces/security_account.go | 6 +++--- internal/provider/security_accounts_data_source.go | 6 +++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/examples/data-sources/netapp-ontap_security_accounts/data-source.tf b/examples/data-sources/netapp-ontap_security_accounts/data-source.tf index 27f68572..9d86fa1e 100644 --- a/examples/data-sources/netapp-ontap_security_accounts/data-source.tf +++ b/examples/data-sources/netapp-ontap_security_accounts/data-source.tf @@ -3,5 +3,6 @@ data "netapp-ontap_security_accounts_data_source" "security_accounts" { cx_profile_name = "cluster4" filter = { name = "vsadmin" + svm_name = "testImport" } } diff --git a/internal/interfaces/security_account.go b/internal/interfaces/security_account.go index ab51f319..d5f6b7c3 100644 --- a/internal/interfaces/security_account.go +++ b/internal/interfaces/security_account.go @@ -40,14 +40,14 @@ type SecurityAccountRole struct { // SecurityAccountOwner describes the owner data model using go types for mapping. type SecurityAccountOwner struct { - Name string `mapstructure:"name"` + Name string `mapstructure:"name,omitempty"` UUID string `mapstructure:"uuid,omitempty"` } // SecurityAccountDataSourceFilterModel describes the data source filter data model. type SecurityAccountDataSourceFilterModel struct { - Name string `tfsdk:"name"` - Owner SecurityAccountOwner `tfsdk:"owner,omitempty"` + Name string `mapstructure:"name"` + Owner SecurityAccountOwner `mapstructure:"owner,omitempty"` } func GetSecurityAccountByName(errorHandler *utils.ErrorHandler, r restclient.RestClient, name string, ownerName string) (*SecurityAccountGetDataModelONTAP, error) { diff --git a/internal/provider/security_accounts_data_source.go b/internal/provider/security_accounts_data_source.go index e8e82ad9..132f58b7 100644 --- a/internal/provider/security_accounts_data_source.go +++ b/internal/provider/security_accounts_data_source.go @@ -194,7 +194,11 @@ func (d *SecurityAccountsDataSource) Read(ctx context.Context, req datasource.Re if data.Filter.SVMName.IsNull() { filter = &interfaces.SecurityAccountDataSourceFilterModel{ Name: data.Filter.Name.ValueString(), + Owner: interfaces.SecurityAccountOwner{ + Name: "*", + }, } + tflog.Debug(errorHandler.Ctx, fmt.Sprintf("if security account filter: %+v", filter)) } else { filter = &interfaces.SecurityAccountDataSourceFilterModel{ Name: data.Filter.Name.ValueString(), @@ -202,8 +206,8 @@ func (d *SecurityAccountsDataSource) Read(ctx context.Context, req datasource.Re Name: data.Filter.SVMName.ValueString(), }, } + tflog.Debug(errorHandler.Ctx, fmt.Sprintf("else security account filter: %+v", filter)) } - } tflog.Debug(errorHandler.Ctx, fmt.Sprintf("security account filter: %+v", filter)) restInfo, err := interfaces.GetSecurityAccounts(errorHandler, *client, filter) From 55c8ec718734c7c6a514528efbed4154a13cb0fc Mon Sep 17 00:00:00 2001 From: Chris Archibald Date: Thu, 7 Dec 2023 15:33:49 -0800 Subject: [PATCH 06/12] working accounts --- internal/interfaces/security_account.go | 16 ++++------- .../provider/security_account_data_source.go | 7 +++++ .../provider/security_accounts_data_source.go | 28 ++++--------------- 3 files changed, 19 insertions(+), 32 deletions(-) diff --git a/internal/interfaces/security_account.go b/internal/interfaces/security_account.go index d5f6b7c3..fff6d2d3 100644 --- a/internal/interfaces/security_account.go +++ b/internal/interfaces/security_account.go @@ -46,13 +46,15 @@ type SecurityAccountOwner struct { // SecurityAccountDataSourceFilterModel describes the data source filter data model. type SecurityAccountDataSourceFilterModel struct { - Name string `mapstructure:"name"` - Owner SecurityAccountOwner `mapstructure:"owner,omitempty"` + Name string `mapstructure:"name"` + Owner *SecurityAccountOwner `mapstructure:"owner,omitempty"` } 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) + query.Set("owner.name", ownerName) statusCode, response, err := r.GetNilOrOneRecord("security/accounts/"+ownerName+"/"+name, query, nil) if err != nil { return nil, errorHandler.MakeAndReportError("Error occurred when getting security account", fmt.Sprintf("error on get security/account: %s", err)) @@ -68,16 +70,10 @@ func GetSecurityAccountByName(errorHandler *utils.ErrorHandler, r restclient.Res return dataOntap, nil } -func GetSecurityAccounts(errorHandler *utils.ErrorHandler, r restclient.RestClient, filter *SecurityAccountDataSourceFilterModel) ([]SecurityAccountGetDataModelONTAP, error) { +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 filter != nil { - var filterMap map[string]interface{} - if error := mapstructure.Decode(filter, &filterMap); error != nil { - return nil, errorHandler.MakeAndReportError("Error occurred when decoding security account filter", fmt.Sprintf("error on decoding security/account filter: %s", error)) - } - query.SetValues(filterMap) - } + tflog.Debug(errorHandler.Ctx, fmt.Sprintf("security account filter: %+v", query)) statusCode, response, err := r.GetZeroOrMoreRecords("security/accounts", query, nil) if err != nil { diff --git a/internal/provider/security_account_data_source.go b/internal/provider/security_account_data_source.go index 807ab4f4..7f1d3eaa 100644 --- a/internal/provider/security_account_data_source.go +++ b/internal/provider/security_account_data_source.go @@ -38,6 +38,7 @@ type SecurityAccountDataSourceModel struct { 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. @@ -78,6 +79,10 @@ func (d *SecurityAccountDataSource) Schema(ctx context.Context, req datasource.S MarkdownDescription: "SecurityAccount name", Required: true, }, + "id": schema.StringAttribute{ + MarkdownDescription: "SecurityAccount id", + Computed: true, + }, "owner": schema.SingleNestedAttribute{ MarkdownDescription: "SecurityAccount owner", Computed: true, @@ -188,6 +193,8 @@ func (d *SecurityAccountDataSource) Read(ctx context.Context, req datasource.Rea } 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), diff --git a/internal/provider/security_accounts_data_source.go b/internal/provider/security_accounts_data_source.go index 132f58b7..bc1f573a 100644 --- a/internal/provider/security_accounts_data_source.go +++ b/internal/provider/security_accounts_data_source.go @@ -88,6 +88,10 @@ func (d *SecurityAccountsDataSource) Schema(ctx context.Context, req datasource. MarkdownDescription: "SecurityAccount name", Required: true, }, + "id": schema.StringAttribute{ + MarkdownDescription: "SecurityAccount id", + Computed: true, + }, "owner": schema.SingleNestedAttribute{ MarkdownDescription: "SecurityAccount owner", Computed: true, @@ -189,28 +193,7 @@ func (d *SecurityAccountsDataSource) Read(ctx context.Context, req datasource.Re return } - var filter *interfaces.SecurityAccountDataSourceFilterModel = nil - if data.Filter != nil { - if data.Filter.SVMName.IsNull() { - filter = &interfaces.SecurityAccountDataSourceFilterModel{ - Name: data.Filter.Name.ValueString(), - Owner: interfaces.SecurityAccountOwner{ - Name: "*", - }, - } - tflog.Debug(errorHandler.Ctx, fmt.Sprintf("if security account filter: %+v", filter)) - } else { - filter = &interfaces.SecurityAccountDataSourceFilterModel{ - Name: data.Filter.Name.ValueString(), - Owner: interfaces.SecurityAccountOwner{ - Name: data.Filter.SVMName.ValueString(), - }, - } - tflog.Debug(errorHandler.Ctx, fmt.Sprintf("else security account filter: %+v", filter)) - } - } - tflog.Debug(errorHandler.Ctx, fmt.Sprintf("security account filter: %+v", filter)) - restInfo, err := interfaces.GetSecurityAccounts(errorHandler, *client, filter) + restInfo, err := interfaces.GetSecurityAccounts(errorHandler, *client, data.Filter.SVMName.ValueString(), data.Filter.Name.ValueString()) if err != nil { // error reporting done inside GetSecurityAccounts return @@ -220,6 +203,7 @@ func (d *SecurityAccountsDataSource) Read(ctx context.Context, req datasource.Re 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), From 3118c69b4ef02c44d277f55120e75616053a5116 Mon Sep 17 00:00:00 2001 From: Chris Archibald Date: Tue, 12 Dec 2023 12:59:29 -0800 Subject: [PATCH 07/12] add data source for accounts --- .../security_account_data_source.md | 77 +++++++++++++++ .../security_accounts_data_source.md | 94 +++++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 docs/data-sources/security_account_data_source.md create mode 100644 docs/data-sources/security_accounts_data_source.md 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 + +### 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 + + +### Nested Schema for `owner` + +Required: + +- `name` (String) SecurityAccount owner name + +Read-Only: + +- `uuid` (String) SecurityAccount owner uuid + + + +### 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 + + + +### 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 + +### 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)) + + +### Nested Schema for `filter` + +Optional: + +- `name` (String) SecurityAccount name +- `svm_name` (String) SecurityAccount svm name (Owner name) + + + +### 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 + + +### 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 + + + +### Nested Schema for `security_accounts.owner` + +Read-Only: + +- `name` (String) SecurityAccount owner name +- `uuid` (String) SecurityAccount owner uuid + + + +### Nested Schema for `security_accounts.role` + +Read-Only: + +- `name` (String) SecurityAccount role name + + From b73180914a4e98f1c0fb64e25533ec8bf70f1cc1 Mon Sep 17 00:00:00 2001 From: Chris Archibald Date: Tue, 12 Dec 2023 13:07:10 -0800 Subject: [PATCH 08/12] fix lint issues --- internal/interfaces/security_account.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/interfaces/security_account.go b/internal/interfaces/security_account.go index fff6d2d3..aeda6346 100644 --- a/internal/interfaces/security_account.go +++ b/internal/interfaces/security_account.go @@ -50,6 +50,7 @@ type SecurityAccountDataSourceFilterModel struct { 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"}) @@ -70,6 +71,7 @@ func GetSecurityAccountByName(errorHandler *utils.ErrorHandler, r restclient.Res 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"}) From e222f886ab49ff7830cefe7bf951a81de2253305 Mon Sep 17 00:00:00 2001 From: Chris Archibald Date: Tue, 2 Jan 2024 14:13:13 -0800 Subject: [PATCH 09/12] fixes --- internal/interfaces/security_account.go | 7 ------- internal/provider/security_accounts_data_source.go | 7 ------- 2 files changed, 14 deletions(-) diff --git a/internal/interfaces/security_account.go b/internal/interfaces/security_account.go index aeda6346..21b869f6 100644 --- a/internal/interfaces/security_account.go +++ b/internal/interfaces/security_account.go @@ -8,13 +8,6 @@ import ( "github.com/netapp/terraform-provider-netapp-ontap/internal/utils" ) -// TODO: -// copy this file to match you data source (should match internal/interfaces/security_account.go) -// replace SecurityAccount with the name of the resource, following go conventions, eg IPInterface -// replace security_account with the name of the resource, for logging purposes, eg ip_interface -// replace api_url with API, eg ip/interfaces -// delete these 5 lines - // SecurityAccountGetDataModelONTAP describes the GET record data model using go types for mapping. type SecurityAccountGetDataModelONTAP struct { Name string `mapstructure:"name"` diff --git a/internal/provider/security_accounts_data_source.go b/internal/provider/security_accounts_data_source.go index bc1f573a..fc01f379 100644 --- a/internal/provider/security_accounts_data_source.go +++ b/internal/provider/security_accounts_data_source.go @@ -11,13 +11,6 @@ import ( "github.com/netapp/terraform-provider-netapp-ontap/internal/utils" ) -// TODO: -// copy this file to match you data source (should match internal/provider/security_account_data_source.go) -// replace SecurityAccounts with the name of the resource, following go conventions, eg IPInterfaces -// replace security_accounts with the name of the resource, for logging purposes, eg ip_interfaces -// make sure to create internal/interfaces/security_account.go too) -// delete these 5 lines - // Ensure provider defined types fully satisfy framework interfaces var _ datasource.DataSource = &SecurityAccountsDataSource{} From 9c3fefd5cd858fb1507e7e72bcc719d3d1c68471 Mon Sep 17 00:00:00 2001 From: Chris Archibald Date: Thu, 4 Jan 2024 12:38:31 -0800 Subject: [PATCH 10/12] add cluster scopped users --- .../data-source.tf | 17 ++++++++-- .../data-source.tf | 27 +++++++++++++++- internal/interfaces/security_account.go | 17 ++++++++-- .../provider/security_account_data_source.go | 31 +++++++++++++------ 4 files changed, 77 insertions(+), 15 deletions(-) diff --git a/examples/data-sources/netapp-ontap_security_account/data-source.tf b/examples/data-sources/netapp-ontap_security_account/data-source.tf index 8969d758..511195a3 100644 --- a/examples/data-sources/netapp-ontap_security_account/data-source.tf +++ b/examples/data-sources/netapp-ontap_security_account/data-source.tf @@ -1,8 +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 = "ansibleSVM" + name = "carchi-test" } name = "vsadmin" -} +} \ 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 index 9d86fa1e..7cf149e4 100644 --- a/examples/data-sources/netapp-ontap_security_accounts/data-source.tf +++ b/examples/data-sources/netapp-ontap_security_accounts/data-source.tf @@ -1,8 +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 = "testImport" + svm_name = "carchi-test" } } + diff --git a/internal/interfaces/security_account.go b/internal/interfaces/security_account.go index 21b869f6..669510f9 100644 --- a/internal/interfaces/security_account.go +++ b/internal/interfaces/security_account.go @@ -48,8 +48,15 @@ func GetSecurityAccountByName(errorHandler *utils.ErrorHandler, r restclient.Res query := r.NewQuery() query.Fields([]string{"name", "owner", "locked", "comment", "role", "scope", "applications"}) query.Set("name", name) - query.Set("owner.name", ownerName) - statusCode, response, err := r.GetNilOrOneRecord("security/accounts/"+ownerName+"/"+name, query, nil) + 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)) } @@ -68,6 +75,12 @@ func GetSecurityAccountByName(errorHandler *utils.ErrorHandler, r restclient.Res 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) diff --git a/internal/provider/security_account_data_source.go b/internal/provider/security_account_data_source.go index 7f1d3eaa..8f1c5a2c 100644 --- a/internal/provider/security_account_data_source.go +++ b/internal/provider/security_account_data_source.go @@ -119,6 +119,7 @@ func (d *SecurityAccountDataSource) Schema(ctx context.Context, req datasource.S "scope": schema.StringAttribute{ MarkdownDescription: "SecurityAccount scope", Computed: true, + Optional: true, }, "applications": schema.ListNestedAttribute{ MarkdownDescription: "SecurityAccount applications", @@ -179,17 +180,27 @@ func (d *SecurityAccountDataSource) Read(ctx context.Context, req datasource.Rea // error reporting done inside NewClient return } - - svm, err := interfaces.GetSvmByName(errorHandler, *client, data.Owner.Name.ValueString()) - if err != nil { - // error reporting done inside GetSvmByName - 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 + } } - - restInfo, err := interfaces.GetSecurityAccountByName(errorHandler, *client, data.Name.ValueString(), svm.UUID) - if err != nil { - // error reporting done inside GetSecurityAccount - 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) From f461222aed98882ced8f61bb15c2236870666be5 Mon Sep 17 00:00:00 2001 From: Chris Archibald Date: Fri, 19 Jan 2024 13:24:45 -0800 Subject: [PATCH 11/12] fixes --- CHANGELOG.md | 2 ++ scripts/acctest.sh | 11 ++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8887341..7f463882 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ 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 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)) ENHANCEMENTS: diff --git a/scripts/acctest.sh b/scripts/acctest.sh index 1dbe6472..b3f2cf4e 100755 --- a/scripts/acctest.sh +++ b/scripts/acctest.sh @@ -2,11 +2,12 @@ org_dir=$(pwd) export TF_ACC=1 -#export TF_ACC_NETAPP_HOST="" -#export TF_ACC_NETAPP_HOST2=">" -#export TF_ACC_NETAPP_USER="admin" -#export TF_ACC_NETAPP_PASS="" -#export TF_ACC_NETAPP_LICENSE="" +export TF_ACC_NETAPP_HOST="10.193.180.108" +export TF_ACC_NETAPP_HOST2="10.193.176.186" +export TF_ACC_NETAPP_HOST3="10.193.176.186" +export TF_ACC_NETAPP_USER="admin" +export TF_ACC_NETAPP_PASS="netapp1!" +export TF_ACC_NETAPP_LICENSE="SMEXXDBBVAAAAAAAAAAAAAAAAAAA" rm -rf $org_dir/test mkdir $org_dir/test From 37ec925b7fd7a8d8391c530a83ded13fef689f13 Mon Sep 17 00:00:00 2001 From: Chris Archibald Date: Tue, 23 Jan 2024 09:41:47 -0800 Subject: [PATCH 12/12] update --- scripts/acctest.sh | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/scripts/acctest.sh b/scripts/acctest.sh index b3f2cf4e..1dbe6472 100755 --- a/scripts/acctest.sh +++ b/scripts/acctest.sh @@ -2,12 +2,11 @@ org_dir=$(pwd) export TF_ACC=1 -export TF_ACC_NETAPP_HOST="10.193.180.108" -export TF_ACC_NETAPP_HOST2="10.193.176.186" -export TF_ACC_NETAPP_HOST3="10.193.176.186" -export TF_ACC_NETAPP_USER="admin" -export TF_ACC_NETAPP_PASS="netapp1!" -export TF_ACC_NETAPP_LICENSE="SMEXXDBBVAAAAAAAAAAAAAAAAAAA" +#export TF_ACC_NETAPP_HOST="" +#export TF_ACC_NETAPP_HOST2=">" +#export TF_ACC_NETAPP_USER="admin" +#export TF_ACC_NETAPP_PASS="" +#export TF_ACC_NETAPP_LICENSE="" rm -rf $org_dir/test mkdir $org_dir/test