Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: User improvements #3034

Merged
merged 43 commits into from
Sep 3, 2024
Merged
Changes from 1 commit
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
17fba7e
rework users datasource starts here
sfc-gh-asawicki Sep 2, 2024
a45ac4f
Add user describe schema and mapper; rework users datasource
sfc-gh-asawicki Sep 2, 2024
10a3e7b
Pass really basic test
sfc-gh-asawicki Sep 2, 2024
6f1361e
Add comment to handle sensitive in show output schemas
sfc-gh-asawicki Sep 2, 2024
9fef8f1
Add a way to use show_output assertions in data sources test
sfc-gh-asawicki Sep 2, 2024
db4b05a
Add assertions to show output in users data source acc test
sfc-gh-asawicki Sep 2, 2024
09ae8f6
Add assertions to parameters in users data source acc test
sfc-gh-asawicki Sep 2, 2024
71af1da
Check user details
sfc-gh-asawicki Sep 2, 2024
e477ebf
Finish users datasource acceptance tests
sfc-gh-asawicki Sep 2, 2024
fa7156e
Add users data source changes to the migration guide
sfc-gh-asawicki Sep 2, 2024
1787d5e
Update users data source documentation
sfc-gh-asawicki Sep 2, 2024
0643851
Fix failing test
sfc-gh-asawicki Sep 2, 2024
728350f
fix custom diffs for fields with diff supression starts here
sfc-gh-asawicki Sep 2, 2024
f0ea064
Prepare new custom diff function taking the diff suppress into consid…
sfc-gh-asawicki Sep 2, 2024
31b17e7
Use the improved computed if any attribute changed implementation
sfc-gh-asawicki Sep 2, 2024
c54ad5e
Unit test improved computed if any attribute changed
sfc-gh-asawicki Sep 2, 2024
720a04b
Add negative test
sfc-gh-asawicki Sep 2, 2024
f616276
Add description
sfc-gh-asawicki Sep 2, 2024
480305a
Add important TODO
sfc-gh-asawicki Sep 2, 2024
31b2544
Hack resource data
sfc-gh-asawicki Sep 2, 2024
a05e041
Fix unit test
sfc-gh-asawicki Sep 2, 2024
dd66718
Fix hack impl
sfc-gh-asawicki Sep 2, 2024
3e41121
Fix old and new values passed to diff suppress
sfc-gh-asawicki Sep 2, 2024
99fa603
fix custom diffs for fields with diff supression starts here
sfc-gh-asawicki Sep 2, 2024
0d0d598
Prepare new custom diff function taking the diff suppress into consid…
sfc-gh-asawicki Sep 2, 2024
e356370
Use the improved computed if any attribute changed implementation
sfc-gh-asawicki Sep 2, 2024
1ae8ab4
Unit test improved computed if any attribute changed
sfc-gh-asawicki Sep 2, 2024
cddde7d
Add negative test
sfc-gh-asawicki Sep 2, 2024
3c36974
Add description
sfc-gh-asawicki Sep 2, 2024
4b90b49
Add important TODO
sfc-gh-asawicki Sep 2, 2024
a7cb8cf
Hack resource data
sfc-gh-asawicki Sep 2, 2024
50706e5
Fix unit test
sfc-gh-asawicki Sep 2, 2024
a9831f7
Fix hack impl
sfc-gh-asawicki Sep 2, 2024
85d1e27
Fix old and new values passed to diff suppress
sfc-gh-asawicki Sep 2, 2024
82649d4
Fix saml2 tests after changes
sfc-gh-asawicki Sep 3, 2024
4b5dc10
Fix warehouse and user
sfc-gh-asawicki Sep 3, 2024
0d9c1b0
Fix tests
sfc-gh-asawicki Sep 3, 2024
5563a6a
Merge remote-tracking branch 'origin/fix-custom-diffs-for-fields-with…
sfc-gh-jcieslak Sep 3, 2024
24aaeeb
rework users datasource starts here
sfc-gh-asawicki Sep 2, 2024
b803f0b
fix custom diffs for fields with diff supression starts here
sfc-gh-asawicki Sep 2, 2024
da45481
Add user improvements
sfc-gh-jcieslak Sep 3, 2024
fc1e8ce
Add user improvements
sfc-gh-jcieslak Sep 3, 2024
74107d8
Add user improvements
sfc-gh-jcieslak Sep 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add user describe schema and mapper; rework users datasource
sfc-gh-asawicki committed Sep 2, 2024
commit a45ac4ff5fc5f6a61e07c7e8a8ba513da6778b2c
235 changes: 124 additions & 111 deletions pkg/datasources/users.go
Original file line number Diff line number Diff line change
@@ -2,93 +2,87 @@ package datasources

import (
"context"
"fmt"
"log"
"strings"

"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider"

"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers"
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/resources"
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/schemas"
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

var usersSchema = map[string]*schema.Schema{
"pattern": {
Type: schema.TypeString,
Required: true,
Description: "Users pattern for which to return metadata. Please refer to LIKE keyword from " +
"snowflake documentation : https://docs.snowflake.com/en/sql-reference/sql/show-users.html#parameters",
"with_describe": {
Type: schema.TypeBool,
Optional: true,
Default: true,
Description: "Runs DESC USER for each user returned by SHOW USERS. The output of describe is saved to the description field. By default this value is set to true.",
},
"users": {
"with_parameters": {
Type: schema.TypeBool,
Optional: true,
Default: true,
Description: "Runs SHOW PARAMETERS FOR USER for each user returned by SHOW USERS. The output of describe is saved to the parameters field as a map. By default this value is set to true.",
},
"like": {
Type: schema.TypeString,
Optional: true,
Description: "Filters the output with **case-insensitive** pattern, with support for SQL wildcard characters (`%` and `_`).",
},
"starts_with": {
Type: schema.TypeString,
Optional: true,
Description: "Filters the output with **case-sensitive** characters indicating the beginning of the object name.",
},
"limit": {
Type: schema.TypeList,
Computed: true,
Description: "The users in the database",
Optional: true,
Description: "Limits the number of rows returned. If the `limit.from` is set, then the limit wll start from the first element matched by the expression. The expression is only used to match with the first element, later on the elements are not matched by the prefix, but you can enforce a certain pattern with `starts_with` or `like`.",
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Computed: true,
},
"login_name": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"comment": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"disabled": {
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"default_warehouse": {
Type: schema.TypeString,
Optional: true,
Computed: true,
"rows": {
Type: schema.TypeInt,
Required: true,
Description: "The maximum number of rows to return.",
},
"default_namespace": {
Type: schema.TypeString,
Optional: true,
Computed: true,
"from": {
Type: schema.TypeString,
Optional: true,
Description: "Specifies a **case-sensitive** pattern that is used to match object name. After the first match, the limit on the number of rows will be applied.",
},
"default_role": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"default_secondary_roles": {
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
Computed: true,
},
"has_rsa_public_key": {
Type: schema.TypeBool,
Computed: true,
},
"email": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"display_name": {
Type: schema.TypeString,
Computed: true,
Optional: true,
},
},
},
"users": {
Type: schema.TypeList,
Computed: true,
Description: "Holds the aggregated output of all user details queries.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
resources.ShowOutputAttributeName: {
Type: schema.TypeList,
Computed: true,
Description: "Holds the output of SHOW USERS.",
Elem: &schema.Resource{
Schema: schemas.ShowUserSchema,
},
},
"first_name": {
Type: schema.TypeString,
Optional: true,
Computed: true,
resources.DescribeOutputAttributeName: {
Type: schema.TypeList,
Computed: true,
Description: "Holds the output of DESCRIBE USER.",
Elem: &schema.Resource{
Schema: schemas.UserDescribeSchema,
},
},
"last_name": {
Type: schema.TypeString,
Optional: true,
Computed: true,
resources.ParametersAttributeName: {
Type: schema.TypeList,
Computed: true,
Description: "Holds the output of SHOW PARAMETERS FOR USER.",
Elem: &schema.Resource{
Schema: schemas.ShowUserParametersSchema,
},
},
},
},
@@ -97,57 +91,76 @@ var usersSchema = map[string]*schema.Schema{

func Users() *schema.Resource {
return &schema.Resource{
Read: ReadUsers,
Schema: usersSchema,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
ReadContext: ReadUsers,
Schema: usersSchema,
Description: "Datasource used to get details of filtered users. Filtering is aligned with the current possibilities for [SHOW USERS](https://docs.snowflake.com/en/sql-reference/sql/show-users) query. The results of SHOW, DESCRIBE, and SHOW PARAMETERS IN are encapsulated in one output collection.",
}
}

func ReadUsers(d *schema.ResourceData, meta interface{}) error {
func ReadUsers(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
client := meta.(*provider.Context).Client
ctx := context.Background()
var opts sdk.ShowUserOptions

userPattern := d.Get("pattern").(string)
if likePattern, ok := d.GetOk("like"); ok {
opts.Like = &sdk.Like{
Pattern: sdk.String(likePattern.(string)),
}
}

account, err1 := client.ContextFunctions.CurrentAccount(ctx)
region, err2 := client.ContextFunctions.CurrentRegion(ctx)
if err1 != nil || err2 != nil {
log.Print("[DEBUG] unable to retrieve current account")
d.SetId("")
return nil
if startsWith, ok := d.GetOk("starts_with"); ok {
opts.StartsWith = sdk.String(startsWith.(string))
}

d.SetId(fmt.Sprintf("%s.%s", account, region))
extractedUsers, err := client.Users.Show(ctx, &sdk.ShowUserOptions{
Like: &sdk.Like{Pattern: sdk.String(userPattern)},
})
if limit, ok := d.GetOk("limit"); ok && len(limit.([]any)) == 1 {
limitMap := limit.([]any)[0].(map[string]any)

rows := limitMap["rows"].(int)
opts.Limit = &rows

if from, ok := limitMap["from"].(string); ok {
opts.From = &from
}
}

users, err := client.Users.Show(ctx, &opts)
if err != nil {
log.Printf("[DEBUG] no users found in account (%s)", d.Id())
d.SetId("")
return nil
return diag.FromErr(err)
}
d.SetId("users_read")

flattenedUsers := make([]map[string]any, len(users))

users := make([]map[string]any, len(extractedUsers))

for i, user := range extractedUsers {
users[i] = map[string]any{
"name": user.Name,
"login_name": user.LoginName,
"comment": user.Comment,
"disabled": user.Disabled,
"default_warehouse": user.DefaultWarehouse,
"default_namespace": user.DefaultNamespace,
"default_role": user.DefaultRole,
"default_secondary_roles": strings.Split(helpers.ListContentToString(user.DefaultSecondaryRoles), ","),
"has_rsa_public_key": user.HasRsaPublicKey,
"email": user.Email,
"display_name": user.DisplayName,
"first_name": user.FirstName,
"last_name": user.LastName,
for i, user := range users {
user := user
var userDescription []map[string]any
if d.Get("with_describe").(bool) {
describeResult, err := client.Users.Describe(ctx, user.ID())
if err != nil {
return diag.FromErr(err)
}
userDescription = schemas.UserDescriptionToSchema(*describeResult)
}

var userParameters []map[string]any
if d.Get("with_parameters").(bool) {
parameters, err := client.Users.ShowParameters(ctx, user.ID())
if err != nil {
return diag.FromErr(err)
}
userParameters = []map[string]any{schemas.UserParametersToSchema(parameters)}
}

flattenedUsers[i] = map[string]any{
resources.ShowOutputAttributeName: []map[string]any{schemas.UserToSchema(&user)},
resources.DescribeOutputAttributeName: userDescription,
resources.ParametersAttributeName: userParameters,
}
}

err = d.Set("users", flattenedUsers)
if err != nil {
return diag.FromErr(err)
}

return d.Set("users", users)
return nil
}
231 changes: 231 additions & 0 deletions pkg/schemas/user_describe.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
package schemas

import (
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

// UserDescribeSchema represents output of DESCRIBE query for the single UserDetails.
var UserDescribeSchema = map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Computed: true,
},
"comment": {
Type: schema.TypeString,
Computed: true,
},
"display_name": {
Type: schema.TypeString,
Computed: true,
},
"login_name": {
Type: schema.TypeString,
Computed: true,
},
"first_name": {
Type: schema.TypeString,
Computed: true,
},
"middle_name": {
Type: schema.TypeString,
Computed: true,
},
"last_name": {
Type: schema.TypeString,
Computed: true,
},
"email": {
Type: schema.TypeString,
Computed: true,
},
"password": {
Type: schema.TypeString,
Computed: true,
},
"must_change_password": {
Type: schema.TypeBool,
Computed: true,
},
"disabled": {
Type: schema.TypeBool,
Computed: true,
},
"snowflake_lock": {
Type: schema.TypeBool,
Computed: true,
},
"snowflake_support": {
Type: schema.TypeBool,
Computed: true,
},
"days_to_expiry": {
Type: schema.TypeFloat,
Computed: true,
},
"mins_to_unlock": {
Type: schema.TypeInt,
Computed: true,
},
"default_warehouse": {
Type: schema.TypeString,
Computed: true,
},
"default_namespace": {
Type: schema.TypeString,
Computed: true,
},
"default_role": {
Type: schema.TypeString,
Computed: true,
},
"default_secondary_roles": {
Type: schema.TypeString,
Computed: true,
},
"ext_authn_duo": {
Type: schema.TypeBool,
Computed: true,
},
"ext_authn_uid": {
Type: schema.TypeString,
Computed: true,
},
"mins_to_bypass_mfa": {
Type: schema.TypeInt,
Computed: true,
},
"mins_to_bypass_network_policy": {
Type: schema.TypeInt,
Computed: true,
},
"rsa_public_key": {
Type: schema.TypeString,
Computed: true,
},
"rsa_public_key_fp": {
Type: schema.TypeString,
Computed: true,
},
"rsa_public_key2": {
Type: schema.TypeString,
Computed: true,
},
"rsa_public_key2_fp": {
Type: schema.TypeString,
Computed: true,
},
"password_last_set_time": {
Type: schema.TypeString,
Computed: true,
},
"custom_landing_page_url": {
Type: schema.TypeString,
Computed: true,
},
"custom_landing_page_url_flush_next_ui_load": {
Type: schema.TypeBool,
Computed: true,
},
}

var _ = UserDescribeSchema

func UserDescriptionToSchema(userDetails sdk.UserDetails) []map[string]any {
userDetailsSchema := make(map[string]any)
if userDetails.Name != nil {
userDetailsSchema["name"] = userDetails.Name.Value
}
if userDetails.Comment != nil {
userDetailsSchema["comment"] = userDetails.Comment.Value
}
if userDetails.DisplayName != nil {
userDetailsSchema["display_name"] = userDetails.DisplayName.Value
}
if userDetails.LoginName != nil {
userDetailsSchema["login_name"] = userDetails.LoginName.Value
}
if userDetails.FirstName != nil {
userDetailsSchema["first_name"] = userDetails.FirstName.Value
}
if userDetails.MiddleName != nil {
userDetailsSchema["middle_name"] = userDetails.MiddleName.Value
}
if userDetails.LastName != nil {
userDetailsSchema["last_name"] = userDetails.LastName.Value
}
if userDetails.Email != nil {
userDetailsSchema["email"] = userDetails.Email.Value
}
if userDetails.Password != nil {
userDetailsSchema["password"] = userDetails.Password.Value
}
if userDetails.MustChangePassword != nil {
userDetailsSchema["must_change_password"] = userDetails.MustChangePassword.Value
}
if userDetails.Disabled != nil {
userDetailsSchema["disabled"] = userDetails.Disabled.Value
}
if userDetails.SnowflakeLock != nil {
userDetailsSchema["snowflake_lock"] = userDetails.SnowflakeLock.Value
}
if userDetails.SnowflakeSupport != nil {
userDetailsSchema["snowflake_support"] = userDetails.SnowflakeSupport.Value
}
if userDetails.DaysToExpiry != nil && userDetails.DaysToExpiry.Value != nil {
userDetailsSchema["days_to_expiry"] = *userDetails.DaysToExpiry.Value
}
if userDetails.MinsToUnlock != nil && userDetails.MinsToUnlock.Value != nil {
userDetailsSchema["mins_to_unlock"] = *userDetails.MinsToUnlock.Value
}
if userDetails.DefaultWarehouse != nil {
userDetailsSchema["default_warehouse"] = userDetails.DefaultWarehouse.Value
}
if userDetails.DefaultNamespace != nil {
userDetailsSchema["default_namespace"] = userDetails.DefaultNamespace.Value
}
if userDetails.DefaultRole != nil {
userDetailsSchema["default_role"] = userDetails.DefaultRole.Value
}
if userDetails.DefaultSecondaryRoles != nil {
userDetailsSchema["default_secondary_roles"] = userDetails.DefaultSecondaryRoles.Value
}
if userDetails.ExtAuthnDuo != nil {
userDetailsSchema["ext_authn_duo"] = userDetails.ExtAuthnDuo.Value
}
if userDetails.ExtAuthnUid != nil {
userDetailsSchema["ext_authn_uid"] = userDetails.ExtAuthnUid.Value
}
if userDetails.MinsToBypassMfa != nil && userDetails.MinsToBypassMfa.Value != nil {
userDetailsSchema["mins_to_bypass_mfa"] = userDetails.MinsToBypassMfa.Value
}
if userDetails.MinsToBypassNetworkPolicy != nil && userDetails.MinsToBypassNetworkPolicy.Value != nil {
userDetailsSchema["mins_to_bypass_network_policy"] = userDetails.MinsToBypassNetworkPolicy.Value
}
if userDetails.RsaPublicKey != nil {
userDetailsSchema["rsa_public_key"] = userDetails.RsaPublicKey.Value
}
if userDetails.RsaPublicKeyFp != nil {
userDetailsSchema["rsa_public_key_fp"] = userDetails.RsaPublicKeyFp.Value
}
if userDetails.RsaPublicKey2 != nil {
userDetailsSchema["rsa_public_key2"] = userDetails.RsaPublicKey2.Value
}
if userDetails.RsaPublicKey2Fp != nil {
userDetailsSchema["rsa_public_key2_fp"] = userDetails.RsaPublicKey2Fp.Value
}
if userDetails.PasswordLastSetTime != nil {
userDetailsSchema["password_last_set_time"] = userDetails.PasswordLastSetTime.Value
}
if userDetails.CustomLandingPageUrl != nil {
userDetailsSchema["custom_landing_page_url"] = userDetails.CustomLandingPageUrl.Value
}
if userDetails.CustomLandingPageUrlFlushNextUiLoad != nil {
userDetailsSchema["custom_landing_page_url_flush_next_ui_load"] = userDetails.CustomLandingPageUrlFlushNextUiLoad.Value
}
return []map[string]any{
userDetailsSchema,
}
}

var _ = UserDescriptionToSchema