diff --git a/CHANGELOG.md b/CHANGELOG.md index d3d06f18..2d11cd92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ FEATURES: * **New Data Source:** `netapp-ontap_volumes_files` ([#8](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/8)) * **New Data Source:** `netapp-ontap_quota_rules` ([#135](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/135)) * **New Data Source:** `netapp-ontap_quota_rule` ([#135](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/135)) +* **New Data Source:** `netapp-ontap_security_role` ([#139](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/139)) +* **New Data Source:** `netapp-ontap_security_roles` ([#139](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/139)) * **New Data Source:** `netapp-ontap_security_login_message` ([#17](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/17)) * **New Data Source:** `netapp-ontap_security_login_messages` ([#17](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/17)) * **New Resource:** `netapp-ontap_volume_efficiency_policies` ([#80](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/80)) diff --git a/docs/data-sources/security_role.md b/docs/data-sources/security_role.md new file mode 100644 index 00000000..a2977ca0 --- /dev/null +++ b/docs/data-sources/security_role.md @@ -0,0 +1,44 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "netapp-ontap_security_role Data Source - terraform-provider-netapp-ontap" +subcategory: "Security" +description: |- + Retrieves a Security role +--- + +# netapp-ontap_security_role (Data Source) + +SecurityRole data source + +## Example Usage +```terraform +data "netapp-ontap_security_role" "security_role" { + # required to know which system to interface with + cx_profile_name = "cluster4" + name = "vsadmin" + svm_name = "acc_test" +} +``` + + +## Schema + +### Required + +- `cx_profile_name` (String) Connection profile name +- `name` (String) SecurityRole name +- `svm_name` (String) IPInterface svm name + +### Optional + +- `builtin` (Boolean) Indicates if this is a built-in (pre-defined) role which cannot be modified or deleted. +- `privileges` (Attributes Set) The list of privileges that this role has been granted. (see [below for nested schema](#nestedatt--privileges)) +- `scope` (String) Scope of the entity. Set to 'cluster' for cluster owned objects and to 'svm' for SVM owned objects. + + +### Nested Schema for `privileges` + +Optional: + +- `access` (String) Access level for the REST endpoint or command/command directory path. If it denotes the access level for a command/command directory path, the only supported enum values are 'none','readonly' and 'all'. +- `path` (String) Either of REST URI/endpoint OR command/command directory path. diff --git a/docs/data-sources/security_roles.md b/docs/data-sources/security_roles.md new file mode 100644 index 00000000..fbe44984 --- /dev/null +++ b/docs/data-sources/security_roles.md @@ -0,0 +1,69 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "netapp-ontap_security_roles Data Source - terraform-provider-netapp-ontap" +subcategory: "Security" +description: |- + Retrieves Security Rules +--- + +# netapp-ontap_security_roles (Data Source) + +SecurityRules data source + +## Example Usage +data "netapp-ontap_security_roles" "security_roles" { + cx_profile_name = "cluster4" + filter = { + svm_name = "svm_1" + scope = "svm" + } +} + + + +## Schema + +### Required + +- `cx_profile_name` (String) Connection profile name + +### Optional + +- `filter` (Attributes) (see [below for nested schema](#nestedatt--filter)) + +### Read-Only + +- `security_roles` (Attributes List) (see [below for nested schema](#nestedatt--security_roles)) + + +### Nested Schema for `filter` + +Optional: + +- `name` (String) SecurityRule name +- `scope` (String) Scope of the entity. Set to 'cluster' for cluster owned objects and to 'svm' for SVM owned objects. +- `svm_name` (String) SecurityRule svm name + + + +### Nested Schema for `security_roles` + +Required: + +- `cx_profile_name` (String) Connection profile name +- `name` (String) SecurityRule name +- `svm_name` (String) IPInterface svm name + +Optional: + +- `builtin` (Boolean) Indicates if this is a built-in (pre-defined) role which cannot be modified or deleted. +- `privileges` (Attributes Set) The list of privileges that this role has been granted. (see [below for nested schema](#nestedatt--security_roles--privileges)) +- `scope` (String) Scope of the entity. Set to 'cluster' for cluster owned objects and to 'svm' for SVM owned objects. + + +### Nested Schema for `security_roles.privileges` + +Optional: + +- `access` (String) Access level for the REST endpoint or command/command directory path. If it denotes the access level for a command/command directory path, the only supported enum values are 'none','readonly' and 'all'. +- `path` (String) Either of REST URI/endpoint OR command/command directory path. diff --git a/examples/data-sources/netapp-ontap_security_role/data-source.tf b/examples/data-sources/netapp-ontap_security_role/data-source.tf new file mode 100644 index 00000000..439b7ce9 --- /dev/null +++ b/examples/data-sources/netapp-ontap_security_role/data-source.tf @@ -0,0 +1,6 @@ +data "netapp-ontap_security_role" "security_role" { + # required to know which system to interface with + cx_profile_name = "cluster4" + name = "vsadmin" + svm_name = "acc_test" +} diff --git a/examples/data-sources/netapp-ontap_security_role/provider.tf b/examples/data-sources/netapp-ontap_security_role/provider.tf new file mode 120000 index 00000000..c6b7138f --- /dev/null +++ b/examples/data-sources/netapp-ontap_security_role/provider.tf @@ -0,0 +1 @@ +../../provider/provider.tf \ No newline at end of file diff --git a/examples/data-sources/netapp-ontap_security_role/terraform.tfvars b/examples/data-sources/netapp-ontap_security_role/terraform.tfvars new file mode 120000 index 00000000..8d9d1c96 --- /dev/null +++ b/examples/data-sources/netapp-ontap_security_role/terraform.tfvars @@ -0,0 +1 @@ +../../provider/terraform.tfvars \ No newline at end of file diff --git a/examples/data-sources/netapp-ontap_security_role/variables.tf b/examples/data-sources/netapp-ontap_security_role/variables.tf new file mode 120000 index 00000000..395ce618 --- /dev/null +++ b/examples/data-sources/netapp-ontap_security_role/variables.tf @@ -0,0 +1 @@ +../../provider/variables.tf \ No newline at end of file diff --git a/examples/data-sources/netapp-ontap_security_roles/data-source.tf b/examples/data-sources/netapp-ontap_security_roles/data-source.tf new file mode 100644 index 00000000..c739bcbe --- /dev/null +++ b/examples/data-sources/netapp-ontap_security_roles/data-source.tf @@ -0,0 +1,8 @@ +data "netapp-ontap_security_roles" "security_roles" { + # required to know which system to interface with + cx_profile_name = "cluster4" + filter = { + svm_name = "acc_test" + scope = "cluster" + } +} diff --git a/examples/data-sources/netapp-ontap_security_roles/provider.tf b/examples/data-sources/netapp-ontap_security_roles/provider.tf new file mode 120000 index 00000000..c6b7138f --- /dev/null +++ b/examples/data-sources/netapp-ontap_security_roles/provider.tf @@ -0,0 +1 @@ +../../provider/provider.tf \ No newline at end of file diff --git a/examples/data-sources/netapp-ontap_security_roles/terraform.tfvars b/examples/data-sources/netapp-ontap_security_roles/terraform.tfvars new file mode 120000 index 00000000..8d9d1c96 --- /dev/null +++ b/examples/data-sources/netapp-ontap_security_roles/terraform.tfvars @@ -0,0 +1 @@ +../../provider/terraform.tfvars \ No newline at end of file diff --git a/examples/data-sources/netapp-ontap_security_roles/variables.tf b/examples/data-sources/netapp-ontap_security_roles/variables.tf new file mode 120000 index 00000000..395ce618 --- /dev/null +++ b/examples/data-sources/netapp-ontap_security_roles/variables.tf @@ -0,0 +1 @@ +../../provider/variables.tf \ No newline at end of file diff --git a/internal/interfaces/security_role.go b/internal/interfaces/security_role.go new file mode 100644 index 00000000..68c40f2c --- /dev/null +++ b/internal/interfaces/security_role.go @@ -0,0 +1,131 @@ +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" +) + +// SecurityRoleGetDataModelONTAP describes the GET record data model using go types for mapping. +type SecurityRoleGetDataModelONTAP struct { + Name string `mapstructure:"name"` + UUID string `mapstructure:"uuid"` + Owner SecurityRoleOwner `mapstructure:"owner"` + Privileges []SecurityRolePrivileges `mapstructure:"privileges"` + Scope string `mapstructure:"scope"` + Builtin bool `mapstructure:"builtin"` +} + +type SecurityRolePrivileges struct { + Access string `mapstructure:"access"` + Path string `mapstructure:"path"` +} + +type SecurityRoleOwner struct { + Name string `mapstructure:"name"` + Id string `mapstructure:"uuid"` +} + +// SecurityRoleResourceBodyDataModelONTAP describes the body data model using go types for mapping. +type SecurityRoleResourceBodyDataModelONTAP struct { + Name string `mapstructure:"name"` + SVM svm `mapstructure:"svm"` +} + +// SecurityRoleDataSourceFilterModel describes the data source data model for queries. +type SecurityRoleDataSourceFilterModel struct { + Name string `mapstructure:"name"` + SVMName string `mapstructure:"owner.name"` + Scope string `mapstructure:"scope"` +} + +// GetSecurityRoleByName to get security_role info +func GetSecurityRoleByName(errorHandler *utils.ErrorHandler, r restclient.RestClient, name string, svmUUID string) (*SecurityRoleGetDataModelONTAP, error) { + api := "security/roles/" + svmUUID + "/" + name + query := r.NewQuery() + query.Set("name", name) + query.Fields([]string{"name", "scope", "owner", "privileges", "builtin"}) + 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_role info", fmt.Sprintf("error on GET %s: %s, statusCode %d", api, err, statusCode)) + } + + var dataONTAP SecurityRoleGetDataModelONTAP + 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_role data source: %#v", dataONTAP)) + return &dataONTAP, nil +} + +// GetSecurityRoles to get security_role info for all resources matching a filter +func GetSecurityRoles(errorHandler *utils.ErrorHandler, r restclient.RestClient, filter *SecurityRoleDataSourceFilterModel) ([]SecurityRoleGetDataModelONTAP, error) { + api := "security/roles" + query := r.NewQuery() + query.Fields([]string{"name", "scope", "owner", "privileges", "builtin"}) + if filter != nil { + var filterMap map[string]interface{} + if err := mapstructure.Decode(filter, &filterMap); err != nil { + return nil, errorHandler.MakeAndReportError("error encoding security_roles 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_roles info", fmt.Sprintf("error on GET %s: %s, statusCode %d", api, err, statusCode)) + } + + var dataONTAP []SecurityRoleGetDataModelONTAP + for _, info := range response { + var record SecurityRoleGetDataModelONTAP + 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_roles data source: %#v", dataONTAP)) + return dataONTAP, nil +} + +// CreateSecurityRole to create security_role +func CreateSecurityRole(errorHandler *utils.ErrorHandler, r restclient.RestClient, body SecurityRoleResourceBodyDataModelONTAP) (*SecurityRoleGetDataModelONTAP, error) { + api := "api_url" + var bodyMap map[string]interface{} + if err := mapstructure.Decode(body, &bodyMap); err != nil { + return nil, errorHandler.MakeAndReportError("error encoding security_role 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_role", fmt.Sprintf("error on POST %s: %s, statusCode %d", api, err, statusCode)) + } + + var dataONTAP SecurityRoleGetDataModelONTAP + if err := mapstructure.Decode(response.Records[0], &dataONTAP); err != nil { + return nil, errorHandler.MakeAndReportError("error decoding security_role info", fmt.Sprintf("error on decode storage/security_roles info: %s, statusCode %d, response %#v", err, statusCode, response)) + } + tflog.Debug(errorHandler.Ctx, fmt.Sprintf("Create security_role source - udata: %#v", dataONTAP)) + return &dataONTAP, nil +} + +// DeleteSecurityRole to delete security_role +func DeleteSecurityRole(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_role", fmt.Sprintf("error on DELETE %s: %s, statusCode %d", api, err, statusCode)) + } + return nil +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 34711790..39c26cf1 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -232,6 +232,8 @@ func (p *ONTAPProvider) DataSources(ctx context.Context) []func() datasource.Dat protocols.NewProtocolsSanLunMapsDataSource, security.NewSecurityAccountDataSource, security.NewSecurityAccountsDataSource, + security.NewSecurityRoleDataSource, + security.NewSecurityRolesDataSource, security.NewSecurityLoginMessageDataSource, security.NewSecurityLoginMessagesDataSource, snapmirror.NewSnapmirrorDataSource, diff --git a/internal/provider/security/security_role_data_source.go b/internal/provider/security/security_role_data_source.go new file mode 100644 index 00000000..7f19b625 --- /dev/null +++ b/internal/provider/security/security_role_data_source.go @@ -0,0 +1,199 @@ +package security + +import ( + "context" + "fmt" + + "github.com/netapp/terraform-provider-netapp-ontap/internal/provider/connection" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "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 = &SecurityRoleDataSource{} + +// NewSecurityRoleDataSource is a helper function to simplify the provider implementation. +func NewSecurityRoleDataSource() datasource.DataSource { + return &SecurityRoleDataSource{ + config: connection.ResourceOrDataSourceConfig{ + Name: "security_role", + }, + } +} + +// SecurityRoleDataSource defines the data source implementation. +type SecurityRoleDataSource struct { + config connection.ResourceOrDataSourceConfig +} + +// SecurityRoleDataSourceModel describes the data source data model. +type SecurityRoleDataSourceModel struct { + CxProfileName types.String `tfsdk:"cx_profile_name"` + Name types.String `tfsdk:"name"` + SVMName types.String `tfsdk:"svm_name"` + Privileges types.Set `tfsdk:"privileges"` + Builtin types.Bool `tfsdk:"builtin"` + Scope types.String `tfsdk:"scope"` +} + +type SecurityRoleOwnerSourceModel struct { + Name types.Int64 `tfsdk:"name"` + Id types.String `tfsdk:"id"` +} + +// Metadata returns the data source type name. +func (d *SecurityRoleDataSource) 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 *SecurityRoleDataSource) 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: "SecurityRole data source", + + Attributes: map[string]schema.Attribute{ + "cx_profile_name": schema.StringAttribute{ + MarkdownDescription: "Connection profile name", + Required: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "SecurityRole name", + Required: true, + }, + "svm_name": schema.StringAttribute{ + MarkdownDescription: "IPInterface svm name", + Required: true, + }, + "privileges": schema.SetNestedAttribute{ + MarkdownDescription: "The list of privileges that this role has been granted.", + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "path": schema.StringAttribute{ + MarkdownDescription: "Either of REST URI/endpoint OR command/command directory path.", + Computed: true, + }, + "access": schema.StringAttribute{ + MarkdownDescription: "Access level for the REST endpoint or command/command directory path. If it denotes the access level for a command/command directory path, the only supported enum values are 'none','readonly' and 'all'.", + Computed: true, + }, + }, + }, + }, + "builtin": schema.BoolAttribute{ + MarkdownDescription: "Indicates if this is a built-in (pre-defined) role which cannot be modified or deleted.", + Computed: true, + }, + "scope": schema.StringAttribute{ + MarkdownDescription: "Scope of the entity. Set to 'cluster' for cluster owned objects and to 'svm' for SVM owned objects.", + Computed: true, + }, + }, + } +} + +// Configure adds the provider configured client to the data source. +func (d *SecurityRoleDataSource) 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.(connection.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 *SecurityRoleDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data SecurityRoleDataSourceModel + + // 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 := connection.GetRestClient(errorHandler, d.config, data.CxProfileName) + if err != nil { + // error reporting done inside NewClient + return + } + + restInfos, err := interfaces.GetSecurityRoles(errorHandler, *client, &interfaces.SecurityRoleDataSourceFilterModel{ + Name: data.Name.ValueString(), + }) + + if err != nil { + // error reporting done inside GetSecurityRoles + return + } + foundRole := false + restInfo := interfaces.SecurityRoleGetDataModelONTAP{} + for _, role := range restInfos { + if role.Name == data.Name.ValueString() { + foundRole = true + restInfo = role + break + } + } + if !foundRole { + resp.Diagnostics.AddError("SecurityRole not found", fmt.Sprintf("SecurityRole %s not found", data.Name.ValueString())) + return + } + + data.Name = types.StringValue(restInfo.Name) + data.Builtin = types.BoolValue(restInfo.Builtin) + data.Scope = types.StringValue(restInfo.Scope) + + // Priviledges + setElements := []attr.Value{} + for _, privilege := range restInfo.Privileges { + nestedElementTypes := map[string]attr.Type{ + "access": types.StringType, + "path": types.StringType, + } + nestedElements := map[string]attr.Value{ + "access": types.StringValue(privilege.Access), + "path": types.StringValue(privilege.Path), + } + objectValue, diags := types.ObjectValue(nestedElementTypes, nestedElements) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + setElements = append(setElements, objectValue) + } + setValue, diags := types.SetValue(types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "access": types.StringType, + "path": types.StringType, + }, + }, setElements) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + data.Privileges = setValue + + // 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/security_roles_data_source.go b/internal/provider/security/security_roles_data_source.go new file mode 100644 index 00000000..11e80e5d --- /dev/null +++ b/internal/provider/security/security_roles_data_source.go @@ -0,0 +1,224 @@ +package security + +import ( + "context" + "fmt" + + "github.com/netapp/terraform-provider-netapp-ontap/internal/provider/connection" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "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 = &SecurityRolesDataSource{} + +// NewSecurityRolesDataSource is a helper function to simplify the provider implementation. +func NewSecurityRolesDataSource() datasource.DataSource { + return &SecurityRolesDataSource{ + config: connection.ResourceOrDataSourceConfig{ + Name: "security_roles", + }, + } +} + +// SecurityRulesDataSource defines the data source implementation. +type SecurityRolesDataSource struct { + config connection.ResourceOrDataSourceConfig +} + +// SecurityRolesDataSourceModel describes the data source data model. +type SecurityRolesDataSourceModel struct { + CxProfileName types.String `tfsdk:"cx_profile_name"` + SecurityRules []SecurityRoleDataSourceModel `tfsdk:"security_roles"` + Filter *SecurityRulesDataSourceFilterModel `tfsdk:"filter"` +} + +// SecurityRulesDataSourceFilterModel describes the data source data model for queries. +type SecurityRulesDataSourceFilterModel struct { + Name types.String `tfsdk:"name"` + SVMName types.String `tfsdk:"svm_name"` + Scope types.String `tfsdk:"scope"` +} + +// Metadata returns the data source type name. +func (d *SecurityRolesDataSource) 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 *SecurityRolesDataSource) 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: "SecurityRules 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: "SecurityRule name", + Optional: true, + }, + "svm_name": schema.StringAttribute{ + MarkdownDescription: "SecurityRule svm name", + Optional: true, + }, + "scope": schema.StringAttribute{ + MarkdownDescription: "Scope of the entity. Set to 'cluster' for cluster owned objects and to 'svm' for SVM owned objects.", + Optional: true, + }, + }, + Optional: true, + }, + "security_roles": 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: "SecurityRule name", + Required: true, + }, + "svm_name": schema.StringAttribute{ + MarkdownDescription: "IPInterface svm name", + Required: true, + }, + "privileges": schema.SetNestedAttribute{ + MarkdownDescription: "The list of privileges that this role has been granted.", + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "path": schema.StringAttribute{ + MarkdownDescription: "Either of REST URI/endpoint OR command/command directory path.", + Computed: true, + }, + "access": schema.StringAttribute{ + MarkdownDescription: "Access level for the REST endpoint or command/command directory path. If it denotes the access level for a command/command directory path, the only supported enum values are 'none','readonly' and 'all'.", + Computed: true, + }, + }, + }, + }, + "builtin": schema.BoolAttribute{ + MarkdownDescription: "Indicates if this is a built-in (pre-defined) role which cannot be modified or deleted.", + Computed: true, + }, + "scope": schema.StringAttribute{ + MarkdownDescription: "Scope of the entity. Set to 'cluster' for cluster owned objects and to 'svm' for SVM owned objects.", + Computed: true, + }, + }, + }, + Computed: true, + MarkdownDescription: "", + }, + }, + } +} + +// Configure adds the provider configured client to the data source. +func (d *SecurityRolesDataSource) 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.(connection.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 *SecurityRolesDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data SecurityRolesDataSourceModel + + // 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 := connection.GetRestClient(errorHandler, d.config, data.CxProfileName) + if err != nil { + // error reporting done inside NewClient + return + } + + var filter *interfaces.SecurityRoleDataSourceFilterModel = nil + if data.Filter != nil { + filter = &interfaces.SecurityRoleDataSourceFilterModel{ + Name: data.Filter.Name.ValueString(), + SVMName: data.Filter.SVMName.ValueString(), + Scope: data.Filter.Scope.ValueString(), + } + } + restInfo, err := interfaces.GetSecurityRoles(errorHandler, *client, filter) + if err != nil { + // error reporting done inside GetSecurityRoles + return + } + + data.SecurityRules = make([]SecurityRoleDataSourceModel, len(restInfo)) + for index, record := range restInfo { + data.SecurityRules[index] = SecurityRoleDataSourceModel{ + CxProfileName: types.String(data.CxProfileName), + Name: types.StringValue(record.Name), + } + data.SecurityRules[index].Scope = types.StringValue(record.Scope) + data.SecurityRules[index].Builtin = types.BoolValue(record.Builtin) + + setElements := []attr.Value{} + for _, privilege := range record.Privileges { + nestedElementTypes := map[string]attr.Type{ + "access": types.StringType, + "path": types.StringType, + } + nestedElements := map[string]attr.Value{ + "access": types.StringValue(privilege.Access), + "path": types.StringValue(privilege.Path), + } + objectValue, diags := types.ObjectValue(nestedElementTypes, nestedElements) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + setElements = append(setElements, objectValue) + } + setValue, diags := types.SetValue(types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "access": types.StringType, + "path": types.StringType, + }, + }, setElements) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + data.SecurityRules[index].Privileges = setValue + } + + // 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)...) +}