From ff2e01a21ced8d7000ee7d721c77f4509423e14a Mon Sep 17 00:00:00 2001
From: taohe1012 <88763781+taohe1012@users.noreply.github.com>
Date: Sat, 29 Jul 2023 15:14:53 +0800
Subject: [PATCH] PowerScale User Datasource (#5)
---
docs/data-sources/user.md | 106 ++++
.../powerscale_access_zone/provider.tf | 4 +-
.../powerscale_user/datasource.tf | 51 ++
.../data-sources/powerscale_user/provider.tf | 36 ++
go.mod | 1 +
go.sum | 1 +
powerscale/helper/user_helper.go | 91 ++++
powerscale/models/user.go | 83 ++++
powerscale/provider/provider.go | 1 +
powerscale/provider/user_data_source.go | 466 ++++++++++++++++++
powerscale/provider/user_datasource_test.go | 215 ++++++++
11 files changed, 1053 insertions(+), 2 deletions(-)
create mode 100644 docs/data-sources/user.md
create mode 100644 examples/data-sources/powerscale_user/datasource.tf
create mode 100644 examples/data-sources/powerscale_user/provider.tf
create mode 100644 powerscale/helper/user_helper.go
create mode 100644 powerscale/models/user.go
create mode 100644 powerscale/provider/user_data_source.go
create mode 100644 powerscale/provider/user_datasource_test.go
diff --git a/docs/data-sources/user.md b/docs/data-sources/user.md
new file mode 100644
index 00000000..c7af9208
--- /dev/null
+++ b/docs/data-sources/user.md
@@ -0,0 +1,106 @@
+---
+# Copyright (c) 2023 Dell Inc., or its subsidiaries. All Rights Reserved.
+#
+# Licensed under the Mozilla Public License Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://mozilla.org/MPL/2.0/
+#
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+title: "powerscale_user data source"
+linkTitle: "powerscale_user"
+page_title: "powerscale_user Data Source - terraform-provider-powerscale"
+subcategory: ""
+description: |-
+ Data source for reading Users in PowerScale cluster.
+---
+
+# powerscale_user (Data Source)
+
+Data source for reading Users in PowerScale cluster.
+
+
+
+
+## Schema
+
+### Optional
+
+- `filter` (Block, Optional) (see [below for nested schema](#nestedblock--filter))
+
+### Read-Only
+
+- `id` (String) Unique identifier of the user instance.
+- `users` (Attributes List) List of users. (see [below for nested schema](#nestedatt--users))
+
+
+### Nested Schema for `filter`
+
+Optional:
+
+- `cached` (Boolean) If true, only return cached objects.
+- `domain` (String) Filter users by domain.
+- `member_of` (Boolean) Enumerate all users that a group is a member of.
+- `name_prefix` (String) Filter users by name prefix.
+- `names` (Attributes List) List of user identity. (see [below for nested schema](#nestedatt--filter--names))
+- `provider` (String) Filter users by provider.
+- `resolve_names` (Boolean) Resolve names of personas.
+- `zone` (String) Filter users by zone.
+
+
+### Nested Schema for `filter.names`
+
+Optional:
+
+- `name` (String) Specifies a user name.
+- `uid` (Number) Specifies a numeric user identifier.
+
+
+
+
+### Nested Schema for `users`
+
+Optional:
+
+- `dn` (String) Specifies a principal name for the user.
+- `dns_domain` (String) Specifies the DNS domain.
+- `domain` (String) Specifies the domain that the object is part of.
+- `email` (String) Specifies an email address.
+- `enabled` (Boolean) If true, the authenticated user is enabled.
+- `expired` (Boolean) If true, the authenticated user has expired.
+- `expiry` (Number) Specifies the Unix Epoch time at which the authenticated user will expire.
+- `gecos` (String) Specifies the GECOS value, which is usually the full name.
+- `generated_gid` (Boolean) If true, the GID was generated.
+- `generated_uid` (Boolean) If true, the UID was generated.
+- `generated_upn` (Boolean) If true, the UPN was generated.
+- `gid` (String) Specifies a group identifier.
+- `home_directory` (String) Specifies a home directory for the user.
+- `id` (String) Specifies the user ID.
+- `locked` (Boolean) If true, indicates that the account is locked.
+- `max_password_age` (Number) Specifies the maximum time in seconds allowed before the password expires.
+- `name` (String) Specifies a user name.
+- `password_expired` (Boolean) If true, the password has expired.
+- `password_expires` (Boolean) If true, the password is allowed to expire.
+- `password_expiry` (Number) Specifies the time in Unix Epoch seconds that the password will expire.
+- `password_last_set` (Number) Specifies the last time the password was set.
+- `primary_group_sid` (String) Specifies the persona of the primary group.
+- `prompt_password_change` (Boolean) If true, Prompts the user to change their password at the next login.
+- `provider` (String) Specifies the authentication provider that the object belongs to.
+- `sam_account_name` (String) Specifies a user name.
+- `shell` (String) Specifies a path to the shell for the user.
+- `sid` (String) Specifies a security identifier.
+- `type` (String) Specifies the object type.
+- `uid` (String) Specifies a user identifier.
+- `upn` (String) Specifies a principal name for the user.
+- `user_can_change_password` (Boolean) Specifies whether the password for the user can be changed.
+
+Read-Only:
+
+- `roles` (List of String) List of roles.
\ No newline at end of file
diff --git a/examples/data-sources/powerscale_access_zone/provider.tf b/examples/data-sources/powerscale_access_zone/provider.tf
index 3a44c97f..75c69ec0 100644
--- a/examples/data-sources/powerscale_access_zone/provider.tf
+++ b/examples/data-sources/powerscale_access_zone/provider.tf
@@ -28,8 +28,8 @@ provider "powerscale" {
endpoint = var.endpoint
insecure = var.insecure
group = var.group
- volumes_path = var.volumes_path
- volumes_path_permissions = var.volumes_path_permissions
+ volume_path = var.volume_path
+ volume_path_permissions = var.volume_path_permissions
ignore_unresolvable_hosts = var.ignore_unresolvable_hosts
auth_type = var.auth_type
verbose_logging = var.verbose_logging
diff --git a/examples/data-sources/powerscale_user/datasource.tf b/examples/data-sources/powerscale_user/datasource.tf
new file mode 100644
index 00000000..688ba3c9
--- /dev/null
+++ b/examples/data-sources/powerscale_user/datasource.tf
@@ -0,0 +1,51 @@
+/*
+Copyright (c) 2023 Dell Inc., or its subsidiaries. All Rights Reserved.
+
+Licensed under the Mozilla Public License Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://mozilla.org/MPL/2.0/
+
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+data "powerscale_user" "test_user" {
+ filter {
+ names = [
+ # {
+ # uid = 0
+ # },
+ # {
+ # name = "admin"
+ # },
+ {
+ name = "tfaccUserDatasource"
+ uid = 10000
+ }
+ ]
+ cached = false
+ name_prefix = "tfacc"
+ resolve_names = false
+ member_of = false
+ # domain = "testDomain"
+ # zone = "testZone"
+ # provider = "testProvider"
+ }
+}
+
+output "powerscale_user_filter" {
+ value = data.powerscale_user.test_user
+}
+
+data "powerscale_user" "test_all_user" {
+}
+
+output "powerscale_user_all" {
+ value = data.powerscale_user.test_all_user
+}
\ No newline at end of file
diff --git a/examples/data-sources/powerscale_user/provider.tf b/examples/data-sources/powerscale_user/provider.tf
new file mode 100644
index 00000000..75c69ec0
--- /dev/null
+++ b/examples/data-sources/powerscale_user/provider.tf
@@ -0,0 +1,36 @@
+/*
+Copyright (c) 2023 Dell Inc., or its subsidiaries. All Rights Reserved.
+
+Licensed under the Mozilla Public License Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://mozilla.org/MPL/2.0/
+
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+terraform {
+ required_providers {
+ powerscale = {
+ source = "registry.terraform.io/dell/powerscale"
+ }
+ }
+}
+
+provider "powerscale" {
+ username = var.username
+ password = var.password
+ endpoint = var.endpoint
+ insecure = var.insecure
+ group = var.group
+ volume_path = var.volume_path
+ volume_path_permissions = var.volume_path_permissions
+ ignore_unresolvable_hosts = var.ignore_unresolvable_hosts
+ auth_type = var.auth_type
+ verbose_logging = var.verbose_logging
+}
\ No newline at end of file
diff --git a/go.mod b/go.mod
index ea0e071c..f500abe4 100644
--- a/go.mod
+++ b/go.mod
@@ -13,6 +13,7 @@ require (
github.com/hashicorp/terraform-plugin-log v0.9.0
github.com/hashicorp/terraform-plugin-testing v1.3.0
github.com/joho/godotenv v1.5.1
+ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f
)
require (
diff --git a/go.sum b/go.sum
index 88a9f4b0..1f582c44 100644
--- a/go.sum
+++ b/go.sum
@@ -210,6 +210,7 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
diff --git a/powerscale/helper/user_helper.go b/powerscale/helper/user_helper.go
new file mode 100644
index 00000000..06f975de
--- /dev/null
+++ b/powerscale/helper/user_helper.go
@@ -0,0 +1,91 @@
+/*
+Copyright (c) 2023 Dell Inc., or its subsidiaries. All Rights Reserved.
+
+Licensed under the Mozilla Public License Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://mozilla.org/MPL/2.0/
+
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package helper
+
+import (
+ powerscale "dell/powerscale-go-client"
+ "terraform-provider-powerscale/powerscale/models"
+
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+// UpdateUserDataSourceState updates datasource state.
+func UpdateUserDataSourceState(userState *models.UserDataSourceModel, userResponse []powerscale.V1MappingUsersLookupMappingItemUser, roles []powerscale.V1AuthRoleExtended) {
+ for _, user := range userResponse {
+ var model models.UserModel
+ UpdateUserState(&model, user)
+
+ var roleAttrs []attr.Value
+ for _, r := range roles {
+ for _, m := range r.Members {
+ if *m.Id == *user.Uid.Id {
+ roleAttrs = append(roleAttrs, types.StringValue(r.Name))
+ }
+ }
+ }
+ model.Roles, _ = types.ListValue(types.StringType, roleAttrs)
+ userState.Users = append(userState.Users, model)
+ }
+}
+
+// UpdateUserState updates user state.
+func UpdateUserState(model *models.UserModel, user powerscale.V1MappingUsersLookupMappingItemUser) {
+
+ model.Dn = types.StringValue(user.Dn)
+ model.DNSDomain = types.StringValue(user.DnsDomain)
+ model.Domain = types.StringValue(user.Domain)
+ model.Email = types.StringValue(user.Email)
+ model.Gecos = types.StringValue(user.Gecos)
+ model.HomeDirectory = types.StringValue(user.HomeDirectory)
+ model.ID = types.StringValue(user.Id)
+ model.Name = types.StringValue(user.Name)
+ model.Provider = types.StringValue(user.Provider)
+ model.SamAccountName = types.StringValue(user.SamAccountName)
+ model.Shell = types.StringValue(user.Shell)
+ model.Type = types.StringValue(user.Type)
+ model.Upn = types.StringValue(user.Upn)
+ if user.Gid.Id != nil {
+ model.GID = types.StringValue(*user.Gid.Id)
+ }
+ if user.PrimaryGroupSid.Id != nil {
+ model.PrimaryGroupSID = types.StringValue(*user.PrimaryGroupSid.Id)
+ }
+ if user.Sid.Id != nil {
+ model.SID = types.StringValue(*user.Sid.Id)
+ }
+ if user.Uid.Id != nil {
+ model.UID = types.StringValue(*user.Uid.Id)
+ }
+
+ model.Enabled = types.BoolValue(user.Enabled)
+ model.Expired = types.BoolValue(user.Expired)
+ model.GeneratedGID = types.BoolValue(user.GeneratedGid)
+ model.GeneratedUID = types.BoolValue(user.GeneratedUid)
+ model.GeneratedUpn = types.BoolValue(user.GeneratedUpn)
+ model.Locked = types.BoolValue(user.Locked)
+ model.PasswordExpired = types.BoolValue(user.PasswordExpired)
+ model.PasswordExpires = types.BoolValue(user.PasswordExpires)
+ model.PromptPasswordChange = types.BoolValue(user.PromptPasswordChange)
+ model.UserCanChangePassword = types.BoolValue(user.UserCanChangePassword)
+
+ model.Expiry = types.Int64Value(int64(user.Expiry))
+ model.MaxPasswordAge = types.Int64Value(int64(user.MaxPasswordAge))
+ model.PasswordExpiry = types.Int64Value(int64(user.PasswordExpiry))
+ model.PasswordLastSet = types.Int64Value(int64(user.PasswordLastSet))
+}
diff --git a/powerscale/models/user.go b/powerscale/models/user.go
new file mode 100644
index 00000000..3b04af7c
--- /dev/null
+++ b/powerscale/models/user.go
@@ -0,0 +1,83 @@
+/*
+Copyright (c) 2023 Dell Inc., or its subsidiaries. All Rights Reserved.
+
+Licensed under the Mozilla Public License Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://mozilla.org/MPL/2.0/
+
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package models
+
+import "github.com/hashicorp/terraform-plugin-framework/types"
+
+// UserDataSourceModel describes the data source data model.
+type UserDataSourceModel struct {
+ Users []UserModel `tfsdk:"users"`
+ ID types.String `tfsdk:"id"`
+
+ //filter
+ Filter *UserFilterType `tfsdk:"filter"`
+}
+
+// UserModel holds user data source schema attribute details.
+type UserModel struct {
+ Dn types.String `tfsdk:"dn"`
+ DNSDomain types.String `tfsdk:"dns_domain"`
+ Domain types.String `tfsdk:"domain"`
+ Email types.String `tfsdk:"email"`
+ Enabled types.Bool `tfsdk:"enabled"`
+ Expired types.Bool `tfsdk:"expired"`
+ Expiry types.Int64 `tfsdk:"expiry"`
+ Gecos types.String `tfsdk:"gecos"`
+ GeneratedGID types.Bool `tfsdk:"generated_gid"`
+ GeneratedUID types.Bool `tfsdk:"generated_uid"`
+ GeneratedUpn types.Bool `tfsdk:"generated_upn"`
+ GID types.String `tfsdk:"gid"`
+ HomeDirectory types.String `tfsdk:"home_directory"`
+ ID types.String `tfsdk:"id"`
+ Locked types.Bool `tfsdk:"locked"`
+ MaxPasswordAge types.Int64 `tfsdk:"max_password_age"`
+ Name types.String `tfsdk:"name"`
+ PasswordExpired types.Bool `tfsdk:"password_expired"`
+ PasswordExpires types.Bool `tfsdk:"password_expires"`
+ PasswordExpiry types.Int64 `tfsdk:"password_expiry"`
+ PasswordLastSet types.Int64 `tfsdk:"password_last_set"`
+ PrimaryGroupSID types.String `tfsdk:"primary_group_sid"`
+ PromptPasswordChange types.Bool `tfsdk:"prompt_password_change"`
+ Provider types.String `tfsdk:"provider"`
+ SamAccountName types.String `tfsdk:"sam_account_name"`
+ Shell types.String `tfsdk:"shell"`
+ SID types.String `tfsdk:"sid"`
+ Type types.String `tfsdk:"type"`
+ UID types.String `tfsdk:"uid"`
+ Upn types.String `tfsdk:"upn"`
+ UserCanChangePassword types.Bool `tfsdk:"user_can_change_password"`
+ Roles types.List `tfsdk:"roles"`
+}
+
+// UserFilterType holds filter attribute for user.
+type UserFilterType struct {
+ Names []UserMemberItem `tfsdk:"names"`
+ NamePrefix types.String `tfsdk:"name_prefix"`
+ Domain types.String `tfsdk:"domain"`
+ Zone types.String `tfsdk:"zone"`
+ Provider types.String `tfsdk:"provider"`
+ Cached types.Bool `tfsdk:"cached"`
+ ResolveNames types.Bool `tfsdk:"resolve_names"`
+ MemberOf types.Bool `tfsdk:"member_of"`
+}
+
+// UserMemberItem holds identity attribute for a auth member.
+type UserMemberItem struct {
+ Name types.String `tfsdk:"name"`
+ UID types.Int64 `tfsdk:"uid"`
+}
diff --git a/powerscale/provider/provider.go b/powerscale/provider/provider.go
index eae99430..afed83e8 100644
--- a/powerscale/provider/provider.go
+++ b/powerscale/provider/provider.go
@@ -188,6 +188,7 @@ func (p *PscaleProvider) DataSources(ctx context.Context) []func() datasource.Da
return []func() datasource.DataSource{
NewAccessZoneDataSource,
NewClusterDataSource,
+ NewUserDataSource,
}
}
diff --git a/powerscale/provider/user_data_source.go b/powerscale/provider/user_data_source.go
new file mode 100644
index 00000000..bc11d773
--- /dev/null
+++ b/powerscale/provider/user_data_source.go
@@ -0,0 +1,466 @@
+/*
+Copyright (c) 2023 Dell Inc., or its subsidiaries. All Rights Reserved.
+
+Licensed under the Mozilla Public License Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://mozilla.org/MPL/2.0/
+
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package provider
+
+import (
+ "context"
+ powerscale "dell/powerscale-go-client"
+ "fmt"
+ "strings"
+ "terraform-provider-powerscale/client"
+ "terraform-provider-powerscale/powerscale/helper"
+ "terraform-provider-powerscale/powerscale/models"
+
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/datasource"
+ "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+ "golang.org/x/sync/errgroup"
+)
+
+// Ensure provider defined types fully satisfy framework interfaces.
+var (
+ _ datasource.DataSource = &UserDataSource{}
+ _ datasource.DataSourceWithConfigure = &UserDataSource{}
+)
+
+// NewUserDataSource creates a new user data source.
+func NewUserDataSource() datasource.DataSource {
+ return &UserDataSource{}
+}
+
+// UserDataSource defines the data source implementation.
+type UserDataSource struct {
+ client *client.Client
+}
+
+// Metadata describes the data source arguments.
+func (d *UserDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_user"
+}
+
+// Schema describes the data source arguments.
+func (d *UserDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ MarkdownDescription: "Data source for reading Users in PowerScale cluster.",
+ Description: "Data source for reading Users in PowerScale cluster.",
+
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Computed: true,
+ MarkdownDescription: "Unique identifier of the user instance.",
+ Description: "Unique identifier of the user instance.",
+ },
+ "users": schema.ListNestedAttribute{
+ MarkdownDescription: "List of users.",
+ Computed: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "name": schema.StringAttribute{
+ Description: "Specifies a user name.",
+ MarkdownDescription: "Specifies a user name.",
+ Optional: true,
+ },
+ "uid": schema.StringAttribute{
+ Description: "Specifies a user identifier.",
+ MarkdownDescription: "Specifies a user identifier.",
+ Optional: true,
+ },
+ "dn": schema.StringAttribute{
+ Description: "Specifies a principal name for the user.",
+ MarkdownDescription: "Specifies a principal name for the user.",
+ Optional: true,
+ },
+ "dns_domain": schema.StringAttribute{
+ Description: "Specifies the DNS domain.",
+ MarkdownDescription: "Specifies the DNS domain.",
+ Optional: true,
+ },
+ "domain": schema.StringAttribute{
+ Description: "Specifies the domain that the object is part of.",
+ MarkdownDescription: "Specifies the domain that the object is part of.",
+ Optional: true,
+ },
+ "email": schema.StringAttribute{
+ Description: "Specifies an email address.",
+ MarkdownDescription: "Specifies an email address.",
+ Optional: true,
+ },
+ "gecos": schema.StringAttribute{
+ Description: "Specifies the GECOS value, which is usually the full name.",
+ MarkdownDescription: "Specifies the GECOS value, which is usually the full name.",
+ Optional: true,
+ },
+ "gid": schema.StringAttribute{
+ Description: "Specifies a group identifier.",
+ MarkdownDescription: "Specifies a group identifier.",
+ Optional: true,
+ },
+ "home_directory": schema.StringAttribute{
+ Description: "Specifies a home directory for the user.",
+ MarkdownDescription: "Specifies a home directory for the user.",
+ Optional: true,
+ },
+ "id": schema.StringAttribute{
+ Description: "Specifies the user ID.",
+ MarkdownDescription: "Specifies the user ID.",
+ Optional: true,
+ },
+ "primary_group_sid": schema.StringAttribute{
+ Description: "Specifies the persona of the primary group.",
+ MarkdownDescription: "Specifies the persona of the primary group.",
+ Optional: true,
+ },
+ "provider": schema.StringAttribute{
+ Description: "Specifies the authentication provider that the object belongs to.",
+ MarkdownDescription: "Specifies the authentication provider that the object belongs to.",
+ Optional: true,
+ },
+ "sam_account_name": schema.StringAttribute{
+ Description: "Specifies a user name.",
+ MarkdownDescription: "Specifies a user name.",
+ Optional: true,
+ },
+ "shell": schema.StringAttribute{
+ Description: "Specifies a path to the shell for the user.",
+ MarkdownDescription: "Specifies a path to the shell for the user.",
+ Optional: true,
+ },
+ "sid": schema.StringAttribute{
+ Description: "Specifies a security identifier.",
+ MarkdownDescription: "Specifies a security identifier.",
+ Optional: true,
+ },
+ "type": schema.StringAttribute{
+ Description: "Specifies the object type.",
+ MarkdownDescription: "Specifies the object type.",
+ Optional: true,
+ },
+ "upn": schema.StringAttribute{
+ Description: "Specifies a principal name for the user.",
+ MarkdownDescription: "Specifies a principal name for the user.",
+ Optional: true,
+ },
+ "enabled": schema.BoolAttribute{
+ Description: "If true, the authenticated user is enabled.",
+ MarkdownDescription: "If true, the authenticated user is enabled.",
+ Optional: true,
+ },
+ "expired": schema.BoolAttribute{
+ Description: "If true, the authenticated user has expired.",
+ MarkdownDescription: "If true, the authenticated user has expired.",
+ Optional: true,
+ },
+ "generated_gid": schema.BoolAttribute{
+ Description: "If true, the GID was generated.",
+ MarkdownDescription: "If true, the GID was generated.",
+ Optional: true,
+ },
+ "generated_uid": schema.BoolAttribute{
+ Description: "If true, the UID was generated.",
+ MarkdownDescription: "If true, the UID was generated.",
+ Optional: true,
+ },
+ "generated_upn": schema.BoolAttribute{
+ Description: "If true, the UPN was generated.",
+ MarkdownDescription: "If true, the UPN was generated.",
+ Optional: true,
+ },
+ "locked": schema.BoolAttribute{
+ Description: "If true, indicates that the account is locked.",
+ MarkdownDescription: "If true, indicates that the account is locked.",
+ Optional: true,
+ },
+ "password_expired": schema.BoolAttribute{
+ Description: "If true, the password has expired.",
+ MarkdownDescription: "If true, the password has expired.",
+ Optional: true,
+ },
+ "password_expires": schema.BoolAttribute{
+ Description: "If true, the password is allowed to expire.",
+ MarkdownDescription: "If true, the password is allowed to expire.",
+ Optional: true,
+ },
+ "prompt_password_change": schema.BoolAttribute{
+ Description: "If true, Prompts the user to change their password at the next login.",
+ MarkdownDescription: "If true, Prompts the user to change their password at the next login.",
+ Optional: true,
+ },
+ "user_can_change_password": schema.BoolAttribute{
+ Description: "Specifies whether the password for the user can be changed.",
+ MarkdownDescription: "Specifies whether the password for the user can be changed.",
+ Optional: true,
+ },
+ "expiry": schema.Int64Attribute{
+ Description: "Specifies the Unix Epoch time at which the authenticated user will expire.",
+ MarkdownDescription: "Specifies the Unix Epoch time at which the authenticated user will expire.",
+ Optional: true,
+ },
+ "max_password_age": schema.Int64Attribute{
+ Description: "Specifies the maximum time in seconds allowed before the password expires.",
+ MarkdownDescription: "Specifies the maximum time in seconds allowed before the password expires.",
+ Optional: true,
+ },
+ "password_expiry": schema.Int64Attribute{
+ Description: "Specifies the time in Unix Epoch seconds that the password will expire.",
+ MarkdownDescription: "Specifies the time in Unix Epoch seconds that the password will expire.",
+ Optional: true,
+ },
+ "password_last_set": schema.Int64Attribute{
+ Description: "Specifies the last time the password was set.",
+ MarkdownDescription: "Specifies the last time the password was set.",
+ Optional: true,
+ },
+ "roles": schema.ListAttribute{
+ Description: "List of roles.",
+ MarkdownDescription: "List of roles.",
+ ElementType: types.StringType,
+ Computed: true,
+ },
+ },
+ },
+ },
+ },
+ Blocks: map[string]schema.Block{
+ "filter": schema.SingleNestedBlock{
+ Attributes: map[string]schema.Attribute{
+ "names": schema.ListNestedAttribute{
+ Description: "List of user identity.",
+ MarkdownDescription: "List of user identity.",
+ Optional: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "name": schema.StringAttribute{
+ Description: "Specifies a user name.",
+ MarkdownDescription: "Specifies a user name.",
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.LengthAtLeast(1),
+ },
+ },
+ "uid": schema.Int64Attribute{
+ Description: "Specifies a numeric user identifier.",
+ MarkdownDescription: "Specifies a numeric user identifier.",
+ Optional: true,
+ },
+ },
+ },
+ },
+ "name_prefix": schema.StringAttribute{
+ Optional: true,
+ Description: "Filter users by name prefix.",
+ MarkdownDescription: "Filter users by name prefix.",
+ Validators: []validator.String{
+ stringvalidator.LengthAtLeast(1),
+ },
+ },
+ "domain": schema.StringAttribute{
+ Optional: true,
+ Description: "Filter users by domain.",
+ MarkdownDescription: "Filter users by domain.",
+ Validators: []validator.String{
+ stringvalidator.LengthAtLeast(1),
+ },
+ },
+ "zone": schema.StringAttribute{
+ Optional: true,
+ Description: "Filter users by zone.",
+ MarkdownDescription: "Filter users by zone.",
+ Validators: []validator.String{
+ stringvalidator.LengthAtLeast(1),
+ },
+ },
+ "provider": schema.StringAttribute{
+ Optional: true,
+ Description: "Filter users by provider.",
+ MarkdownDescription: "Filter users by provider.",
+ Validators: []validator.String{
+ stringvalidator.LengthAtLeast(1),
+ },
+ },
+ "cached": schema.BoolAttribute{
+ Optional: true,
+ Description: "If true, only return cached objects.",
+ MarkdownDescription: "If true, only return cached objects.",
+ },
+ "resolve_names": schema.BoolAttribute{
+ Optional: true,
+ Description: "Resolve names of personas.",
+ MarkdownDescription: "Resolve names of personas.",
+ },
+ "member_of": schema.BoolAttribute{
+ Optional: true,
+ Description: "Enumerate all users that a group is a member of.",
+ MarkdownDescription: "Enumerate all users that a group is a member of.",
+ },
+ },
+ },
+ },
+ }
+}
+
+// Configure configures the data source.
+func (d *UserDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
+ // Prevent panic if the provider has not been configured.
+ if req.ProviderData == nil {
+ return
+ }
+
+ pscaleClient, ok := req.ProviderData.(*client.Client)
+
+ if !ok {
+ resp.Diagnostics.AddError(
+ "Unexpected Data Source Configure Type",
+ fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
+ )
+
+ return
+ }
+
+ d.client = pscaleClient
+}
+
+// Read reads data from the data source.
+func (d *UserDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+ tflog.Info(ctx, "Reading User data source ")
+
+ var state models.UserDataSourceModel
+
+ // Read Terraform configuration data into the model
+ resp.Diagnostics.Append(req.Config.Get(ctx, &state)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ // start goroutine to cache all roles
+ var eg errgroup.Group
+ var roles []powerscale.V1AuthRoleExtended
+ eg.Go(func() error {
+ roleParams := d.client.PscaleOpenAPIClient.AuthApi.ListAuthv1AuthRoles(ctx)
+ result, _, err := roleParams.Execute()
+ if err != nil {
+ return err
+ }
+
+ for {
+ roles = append(roles, result.Roles...)
+ if result.Resume == nil || *result.Resume == "" {
+ break
+ }
+
+ roleParams = d.client.PscaleOpenAPIClient.AuthApi.ListAuthv1AuthRoles(ctx).Resume(*result.Resume)
+ if result, _, err = roleParams.Execute(); err != nil {
+ return err
+ }
+ }
+
+ return err
+ })
+
+ // cache all users
+ var users []powerscale.V1MappingUsersLookupMappingItemUser
+ userParams := d.client.PscaleOpenAPIClient.AuthApi.ListAuthv1AuthUsers(ctx)
+
+ if state.Filter != nil {
+ if !state.Filter.NamePrefix.IsNull() {
+ userParams = userParams.Filter(state.Filter.NamePrefix.ValueString())
+ }
+ if !state.Filter.Domain.IsNull() {
+ userParams = userParams.Domain(state.Filter.Domain.ValueString())
+ }
+ if !state.Filter.Zone.IsNull() {
+ userParams = userParams.Zone(state.Filter.Zone.ValueString())
+ }
+ if !state.Filter.Provider.IsNull() {
+ userParams = userParams.Provider(state.Filter.Provider.ValueString())
+ }
+ if !state.Filter.Cached.IsNull() {
+ userParams = userParams.Cached(state.Filter.Cached.ValueBool())
+ }
+ if !state.Filter.ResolveNames.IsNull() {
+ userParams = userParams.ResolveNames(state.Filter.ResolveNames.ValueBool())
+ }
+ if !state.Filter.MemberOf.IsNull() {
+ userParams = userParams.QueryMemberOf(state.Filter.MemberOf.ValueBool())
+ }
+ }
+
+ result, _, err := userParams.Execute()
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error getting the list of PowerScale Users",
+ err.Error(),
+ )
+ return
+ }
+
+ for {
+ users = append(users, result.Users...)
+ if result.Resume == nil || *result.Resume == "" {
+ break
+ }
+
+ userParams = d.client.PscaleOpenAPIClient.AuthApi.ListAuthv1AuthUsers(ctx).Resume(*result.Resume)
+ if result, _, err = userParams.Execute(); err != nil {
+ resp.Diagnostics.AddError("Error getting the list of PowerScale Users with resume", err.Error())
+ return
+ }
+ }
+
+ if err := eg.Wait(); err != nil {
+ resp.Diagnostics.AddError("Error getting the list of PowerScale Roles", err.Error())
+ roles = nil
+ }
+
+ // parse user response to state user model
+ helper.UpdateUserDataSourceState(&state, users, roles)
+
+ // filter users by names
+ if state.Filter != nil && len(state.Filter.Names) > 0 {
+ var validUsers []string
+ var filteredUsers []models.UserModel
+
+ for _, user := range state.Users {
+ for _, name := range state.Filter.Names {
+ if (!name.Name.IsNull() && user.Name.Equal(name.Name)) ||
+ (!name.UID.IsNull() && fmt.Sprintf("UID:%d", name.UID.ValueInt64()) == user.UID.ValueString()) {
+ filteredUsers = append(filteredUsers, user)
+ validUsers = append(validUsers, fmt.Sprintf("Name: %s, UID: %s", user.Name, user.UID))
+ continue
+ }
+ }
+ }
+
+ state.Users = filteredUsers
+
+ if len(state.Users) != len(state.Filter.Names) {
+ resp.Diagnostics.AddError(
+ "Error one or more of the filtered user names is not a valid powerscale user.",
+ fmt.Sprintf("Valid users: [%v], filtered list: [%v]", strings.Join(validUsers, " ; "), state.Filter.Names),
+ )
+ }
+ }
+
+ state.ID = types.StringValue("user_datasource")
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
+ tflog.Info(ctx, "Done with Read User data source ")
+}
diff --git a/powerscale/provider/user_datasource_test.go b/powerscale/provider/user_datasource_test.go
new file mode 100644
index 00000000..9e0ed67a
--- /dev/null
+++ b/powerscale/provider/user_datasource_test.go
@@ -0,0 +1,215 @@
+/*
+Copyright (c) 2023 Dell Inc., or its subsidiaries. All Rights Reserved.
+
+Licensed under the Mozilla Public License Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://mozilla.org/MPL/2.0/
+
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package provider
+
+import (
+ "regexp"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+)
+
+func TestAccUserDataSourceFilter(t *testing.T) {
+ var userTerraformName = "data.powerscale_user.test"
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ // filter read testing
+ {
+ Config: ProviderConfig + userFilterDataSourceConfig,
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttrSet(userTerraformName, "users.#"),
+ ),
+ },
+ },
+ })
+}
+
+func TestAccUserDataSourceNames(t *testing.T) {
+ var userTerraformName = "data.powerscale_user.test"
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ // filter by names read testing
+ {
+ Config: ProviderConfig + userNamesDataSourceConfig,
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr(userTerraformName, "users.#", "3"),
+ ),
+ },
+ },
+ })
+}
+
+func TestAccUserDataSourceFilterNames(t *testing.T) {
+ var userTerraformName = "data.powerscale_user.test"
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ // filter with names read testing
+ {
+ Config: ProviderConfig + userFilterNamesDataSourceConfig,
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr(userTerraformName, "users.#", "1"),
+ resource.TestCheckResourceAttr(userTerraformName, "users.0.uid", "UID:10000"),
+ resource.TestCheckResourceAttr(userTerraformName, "users.0.name", "tfaccUserDatasource"),
+ resource.TestCheckResourceAttr(userTerraformName, "users.0.roles.#", "0"),
+ ),
+ },
+ },
+ })
+}
+
+func TestAccUserDataSourceInvalidFilter(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ // filter with invalid filter read testing
+ {
+ Config: ProviderConfig + userInvalidFilterDataSourceConfig,
+ ExpectError: regexp.MustCompile(`.*Error getting the list of PowerScale Users*.`),
+ },
+ },
+ })
+}
+
+func TestAccUserDataSourceInvalidNames(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ // filter with invalid names read testing
+ {
+ Config: ProviderConfig + userInvalidNamesDataSourceConfig,
+ ExpectError: regexp.MustCompile(`.*Error one or more of the filtered user names is not a valid powerscale user*.`),
+ },
+ },
+ })
+}
+
+func TestAccUserDataSourceAll(t *testing.T) {
+ var userTerraformName = "data.powerscale_user.all"
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ // read all testing
+ {
+ Config: ProviderConfig + userAllDataSourceConfig,
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttrSet(userTerraformName, "users.#"),
+ ),
+ },
+ },
+ })
+}
+
+var userFilterDataSourceConfig = `
+data "powerscale_user" "test" {
+ filter {
+ cached = false
+ resolve_names = false
+ member_of = false
+ # domain = ""
+ # zone = ""
+ # provider = ""
+ }
+}
+`
+
+var userFilterNamesDataSourceConfig = `
+data "powerscale_user" "test" {
+ filter {
+ names = [
+ {
+ name = "tfaccUserDatasource"
+ uid = 10000
+ }
+ ]
+ cached = false
+ name_prefix = "tfacc"
+ resolve_names = false
+ member_of = false
+ # domain = "testDomain"
+ # zone = "testZone"
+ # provider = "testProvider"
+ }
+}
+`
+
+var userNamesDataSourceConfig = `
+data "powerscale_user" "test" {
+ filter {
+ names = [
+ {
+ uid = 0
+ },
+ {
+ name = "admin"
+ },
+ {
+ name = "tfaccUserDatasource"
+ uid = 10000
+ }
+ ]
+ }
+}
+`
+var userInvalidFilterDataSourceConfig = `
+data "powerscale_user" "test" {
+ filter {
+ names = [
+ {
+ name = "tfaccUserDatasource"
+ uid = 10000
+ }
+ ]
+ cached = false
+ name_prefix = "tfacc"
+ resolve_names = false
+ member_of = false
+ domain = " "
+ zone = " "
+ provider = " "
+ }
+ }
+`
+
+var userInvalidNamesDataSourceConfig = `
+data "powerscale_user" "test" {
+ filter {
+ names = [
+ {
+ uid = 0
+ },
+ {
+ name = "invalidUser"
+ }
+ ]
+ }
+}
+`
+
+var userAllDataSourceConfig = `
+data "powerscale_user" "all" {
+}
+`