diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index ffd98570d0..d6a0c292c2 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -257,6 +257,8 @@ New fields: - `mins_to_unlock` - `mins_to_bypass_mfa` - `disable_mfa` +- `show_output` - holds the response from `SHOW USERS`. Remember that the field will be only recomputed if one of the user attributes is changed. +- `parameters` - holds the response from `SHOW PARAMETERS IN USER`. Removed fields: - `has_rsa_public_key` @@ -266,8 +268,8 @@ Default changes: - `disabled` Type changes: -- `must_change_password`: bool -> string -- `disabled`: bool -> string +- `must_change_password`: bool -> string (To easily handle three-value logic (true, false, unknown) in provider's configs, read more in https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/751239b7d2fee4757471db6c03b952d4728ee099/v1-preparations/CHANGES_BEFORE_V1.md?plain=1#L24) +- `disabled`: bool -> string (To easily handle three-value logic (true, false, unknown) in provider's configs, read more in https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/751239b7d2fee4757471db6c03b952d4728ee099/v1-preparations/CHANGES_BEFORE_V1.md?plain=1#L24) Validation changes: - `default_secondary_roles` - only 1-element lists with `"ALL"` element are now supported. Check [Snowflake docs](https://docs.snowflake.com/en/sql-reference/sql/create-user#optional-object-properties-objectproperties) for more details. @@ -289,6 +291,27 @@ Changes: Connected issues: [#2902](https://github.com/Snowflake-Labs/terraform-provider-snowflake/pull/2902) +#### *(breaking change)* snowflake_user_public_keys usage with snowflake_user + +`snowflake_user_public_keys` is a resource allowing to set keys for the given user. Before this version, it was possible to have `snowflake_user` and `snowflake_user_public_keys` used next to each other. +Because the logic handling the keys in `snowflake_user` was fixed, it is advised to use `snowflake_user_public_keys` only when user is not managed through terraform. Having both resources configured for the same user will result in improper behavior. + +To migrate, in case of having two resources: +- copy the keys to `rsa_public_key` and `rsa_public_key2` in `snowflake_user` +- remove `snowflake_user_public_keys` from state (following https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/docs/technical-documentation/resource_migration.md#resource-migration) +- remove `snowflake_user_public_keys` from config + +#### *(note)* snowflake_user_password_policy_attachment and other user policies + +`snowflake_user_password_policy_attachment` is not addressed in the current version. +Attaching other user policies is not addressed in the current version. + +Both topics will be addressed in the following versions. + +#### *(note)* user types + +`service` and `legacy_service` user types are currently not supported. They will be supported in the following versions as separate resources (namely `snowflake_service_user` and `snowflake_legacy_service_user`). + ## v0.94.0 ➞ v0.94.1 ### changes in snowflake_schema diff --git a/docs/resources/user.md b/docs/resources/user.md index 9d03f9066d..d28d978cbe 100644 --- a/docs/resources/user.md +++ b/docs/resources/user.md @@ -7,6 +7,14 @@ description: |- !> **V1 release candidate** This resource was reworked and is a release candidate for the V1. We do not expect significant changes in it before the V1. We will welcome any feedback and adjust the resource if needed. Any errors reported will be resolved with a higher priority. We encourage checking this resource out before the V1 release. Please follow the [migration guide](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/MIGRATION_GUIDE.md#v094x--v0950) to use it. +-> **Note** `snowflake_user_password_policy_attachment` will be reworked in the following versions of the provider which may still affect this resource. + +-> **Note** Attaching user policies will be handled in the following versions of the provider which may still affect this resource. + +-> **Note** `service` and `legacy_service` user types are currently not supported. They will be supported in the following versions as separate resources (namely `snowflake_service_user` and `snowflake_legacy_service_user`). + +-> **Note** External changes to `days_to_expiry`, `mins_to_unlock`, and `mins_to_bypass_mfa` are not currently handled by the provider (because the value changes continuously on Snowflake side after setting it). + # snowflake_user (Resource) Resource used to manage user objects. For more information, check [user documentation](https://docs.snowflake.com/en/sql-reference/commands-user-role). @@ -62,12 +70,12 @@ resource "snowflake_user" "user" { - `comment` (String) Specifies a comment for the user. - `date_input_format` (String) Specifies the input format for the DATE data type. For more information, see [Date and time input and output formats](https://docs.snowflake.com/en/sql-reference/date-time-input-output). For more information, check [DATE_INPUT_FORMAT docs](https://docs.snowflake.com/en/sql-reference/parameters#date-input-format). - `date_output_format` (String) Specifies the display format for the DATE data type. For more information, see [Date and time input and output formats](https://docs.snowflake.com/en/sql-reference/date-time-input-output). For more information, check [DATE_OUTPUT_FORMAT docs](https://docs.snowflake.com/en/sql-reference/parameters#date-output-format). -- `days_to_expiry` (Number) Specifies the number of days after which the user status is set to `Expired` and the user is no longer allowed to log in. This is useful for defining temporary users (i.e. users who should only have access to Snowflake for a limited time period). In general, you should not set this property for [account administrators](https://docs.snowflake.com/en/user-guide/security-access-control-considerations.html#label-accountadmin-users) (i.e. users with the `ACCOUNTADMIN` role) because Snowflake locks them out when they become `Expired`. +- `days_to_expiry` (Number) Specifies the number of days after which the user status is set to `Expired` and the user is no longer allowed to log in. This is useful for defining temporary users (i.e. users who should only have access to Snowflake for a limited time period). In general, you should not set this property for [account administrators](https://docs.snowflake.com/en/user-guide/security-access-control-considerations.html#label-accountadmin-users) (i.e. users with the `ACCOUNTADMIN` role) because Snowflake locks them out when they become `Expired`. External changes for this field won't be detected. In case you want to apply external changes, you can re-create the resource manually using "terraform taint". - `default_namespace` (String) Specifies the namespace (database only or database and schema) that is active by default for the user’s session upon login. Note that the CREATE USER operation does not verify that the namespace exists. - `default_role` (String) Specifies the role that is active by default for the user’s session upon login. Note that specifying a default role for a user does **not** grant the role to the user. The role must be granted explicitly to the user using the [GRANT ROLE](https://docs.snowflake.com/en/sql-reference/sql/grant-role) command. In addition, the CREATE USER operation does not verify that the role exists. - `default_secondary_roles` (Set of String) Specifies the set of secondary roles that are active for the user’s session upon login. Currently only ["ALL"] value is supported - more information can be found in [doc](https://docs.snowflake.com/en/sql-reference/sql/create-user#optional-object-properties-objectproperties). - `default_warehouse` (String) Specifies the virtual warehouse that is active by default for the user’s session upon login. Note that the CREATE USER operation does not verify that the warehouse exists. -- `disable_mfa` (String) Allows enabling or disabling [multi-factor authentication](https://docs.snowflake.com/en/user-guide/security-mfa). Available options are: "true" or "false". When the value is not set in the configuration the provider will put "default" there which means to use the Snowflake default for this value. +- `disable_mfa` (String) Allows enabling or disabling [multi-factor authentication](https://docs.snowflake.com/en/user-guide/security-mfa). Available options are: "true" or "false". When the value is not set in the configuration the provider will put "default" there which means to use the Snowflake default for this value. External changes for this field won't be detected. In case you want to apply external changes, you can re-create the resource manually using "terraform taint". - `disabled` (String) Specifies whether the user is disabled, which prevents logging in and aborts all the currently-running queries for the user. Available options are: "true" or "false". When the value is not set in the configuration the provider will put "default" there which means to use the Snowflake default for this value. - `display_name` (String) Name displayed for the user in the Snowflake web interface. - `email` (String, Sensitive) Email address for the user. @@ -87,8 +95,8 @@ resource "snowflake_user" "user" { - `log_level` (String) Specifies the severity level of messages that should be ingested and made available in the active event table. Messages at the specified level (and at more severe levels) are ingested. For more information about log levels, see [Setting log level](https://docs.snowflake.com/en/developer-guide/logging-tracing/logging-log-level). For more information, check [LOG_LEVEL docs](https://docs.snowflake.com/en/sql-reference/parameters#log-level). - `login_name` (String, Sensitive) The name users use to log in. If not supplied, snowflake will use name instead. Login names are always case-insensitive. - `middle_name` (String, Sensitive) Middle name of the user. -- `mins_to_bypass_mfa` (Number) Specifies the number of minutes to temporarily bypass MFA for the user. This property can be used to allow a MFA-enrolled user to temporarily bypass MFA during login in the event that their MFA device is not available. -- `mins_to_unlock` (Number) Specifies the number of minutes until the temporary lock on the user login is cleared. To protect against unauthorized user login, Snowflake places a temporary lock on a user after five consecutive unsuccessful login attempts. When creating a user, this property can be set to prevent them from logging in until the specified amount of time passes. To remove a lock immediately for a user, specify a value of 0 for this parameter. +- `mins_to_bypass_mfa` (Number) Specifies the number of minutes to temporarily bypass MFA for the user. This property can be used to allow a MFA-enrolled user to temporarily bypass MFA during login in the event that their MFA device is not available. External changes for this field won't be detected. In case you want to apply external changes, you can re-create the resource manually using "terraform taint". +- `mins_to_unlock` (Number) Specifies the number of minutes until the temporary lock on the user login is cleared. To protect against unauthorized user login, Snowflake places a temporary lock on a user after five consecutive unsuccessful login attempts. When creating a user, this property can be set to prevent them from logging in until the specified amount of time passes. To remove a lock immediately for a user, specify a value of 0 for this parameter. **Note** because this value changes continuously after setting it, the provider is currently NOT handling the external changes to it. External changes for this field won't be detected. In case you want to apply external changes, you can re-create the resource manually using "terraform taint". - `multi_statement_count` (Number) Number of statements to execute when using the multi-statement capability. For more information, check [MULTI_STATEMENT_COUNT docs](https://docs.snowflake.com/en/sql-reference/parameters#multi-statement-count). - `must_change_password` (String) Specifies whether the user is forced to change their password on next login (including their first/initial login) into the system. Available options are: "true" or "false". When the value is not set in the configuration the provider will put "default" there which means to use the Snowflake default for this value. - `network_policy` (String) Specifies the network policy to enforce for your account. Network policies enable restricting access to your account based on users’ IP address. For more details, see [Controlling network traffic with network policies](https://docs.snowflake.com/en/user-guide/network-policies). Any existing network policy (created using [CREATE NETWORK POLICY](https://docs.snowflake.com/en/sql-reference/sql/create-network-policy)). For more information, check [NETWORK_POLICY docs](https://docs.snowflake.com/en/sql-reference/parameters#network-policy). diff --git a/docs/resources/user_public_keys.md b/docs/resources/user_public_keys.md index 21c23e542a..54a0067069 100644 --- a/docs/resources/user_public_keys.md +++ b/docs/resources/user_public_keys.md @@ -5,6 +5,8 @@ description: |- --- +!> **Important** Starting from v0.95.0, it is advised to use this resource **only** if users are not managed through terraform. Check more in the [migration guide](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/MIGRATION_GUIDE.md#v094x--v0950). + # snowflake_user_public_keys (Resource) diff --git a/pkg/acceptance/helpers/user_client.go b/pkg/acceptance/helpers/user_client.go index 16f9aeb106..dae89b0e39 100644 --- a/pkg/acceptance/helpers/user_client.go +++ b/pkg/acceptance/helpers/user_client.go @@ -103,6 +103,22 @@ func (c *UserClient) SetType(t *testing.T, id sdk.AccountObjectIdentifier, value require.NoError(t, err) } +func (c *UserClient) SetLoginName(t *testing.T, id sdk.AccountObjectIdentifier, newLoginName string) { + t.Helper() + ctx := context.Background() + + err := c.client().Alter(ctx, id, &sdk.AlterUserOptions{ + Set: &sdk.UserSet{ + ObjectProperties: &sdk.UserAlterObjectProperties{ + UserObjectProperties: sdk.UserObjectProperties{ + LoginName: sdk.String(newLoginName), + }, + }, + }, + }) + require.NoError(t, err) +} + func (c *UserClient) UnsetDefaultSecondaryRoles(t *testing.T, id sdk.AccountObjectIdentifier) { t.Helper() ctx := context.Background() diff --git a/pkg/internal/provider/sdkv2enhancements/resource_data.go b/pkg/internal/provider/sdkv2enhancements/resource_data.go index c2f450c908..646fcbecb6 100644 --- a/pkg/internal/provider/sdkv2enhancements/resource_data.go +++ b/pkg/internal/provider/sdkv2enhancements/resource_data.go @@ -2,7 +2,6 @@ package sdkv2enhancements import ( "reflect" - "unsafe" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" @@ -17,9 +16,9 @@ import ( // - terraform.InstanceState and terraform.InstanceDiff are unexported in schema.ResourceDiff, so we get them using reflection func CreateResourceDataFromResourceDiff(resourceSchema schema.InternalMap, diff *schema.ResourceDiff) (*schema.ResourceData, bool) { unexportedState := reflect.ValueOf(diff).Elem().FieldByName("state") - stateFromResourceDiff := reflect.NewAt(unexportedState.Type(), unsafe.Pointer(unexportedState.UnsafeAddr())).Elem().Interface() + stateFromResourceDiff := reflect.NewAt(unexportedState.Type(), unexportedState.Addr().UnsafePointer()).Elem().Interface() unexportedDiff := reflect.ValueOf(diff).Elem().FieldByName("diff") - diffFroResourceDif := reflect.NewAt(unexportedDiff.Type(), unsafe.Pointer(unexportedDiff.UnsafeAddr())).Elem().Interface() + diffFroResourceDif := reflect.NewAt(unexportedDiff.Type(), unexportedDiff.Addr().UnsafePointer()).Elem().Interface() castState, ok := stateFromResourceDiff.(*terraform.InstanceState) if !ok { return nil, false diff --git a/pkg/resources/custom_diffs.go b/pkg/resources/custom_diffs.go index a10c78d4d6..e7c8bce2c3 100644 --- a/pkg/resources/custom_diffs.go +++ b/pkg/resources/custom_diffs.go @@ -86,7 +86,7 @@ func ForceNewIfChangeToEmptyString(key string) schema.CustomizeDiffFunc { // ComputedIfAnyAttributeChanged marks the given fields as computed if any of the listed fields changes. // It takes field-level diffSuppress into consideration based on the schema passed. -// If the field is not found in the given schema, it continues without error. +// If the field is not found in the given schema, it continues without error. Only top level schema fields should be used. func ComputedIfAnyAttributeChanged(resourceSchema map[string]*schema.Schema, key string, changedAttributeKeys ...string) schema.CustomizeDiffFunc { return customdiff.ComputedIf(key, func(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) bool { var result bool @@ -106,7 +106,7 @@ func ComputedIfAnyAttributeChanged(resourceSchema map[string]*schema.Schema, key log.Printf("[DEBUG] ComputedIfAnyAttributeChanged: key %s was changed and the diff is not suppressed", changedKey) result = true } else { - log.Printf("[DEBUG] ComputedIfAnyAttributeChanged: key %s was changed but the diff is suppresed", changedKey) + log.Printf("[DEBUG] ComputedIfAnyAttributeChanged: key %s was changed but the diff is suppressed", changedKey) } } else { log.Printf("[DEBUG] ComputedIfAnyAttributeChanged: key %s was changed and it does not have a diff suppressor", changedKey) diff --git a/pkg/resources/network_policy.go b/pkg/resources/network_policy.go index 8283f22b63..7b590ce169 100644 --- a/pkg/resources/network_policy.go +++ b/pkg/resources/network_policy.go @@ -98,6 +98,7 @@ func NetworkPolicy() *schema.Resource { // The main issue lays in the old Terraform SDK and how its handling DiffSuppression and CustomizeDiff // for complex types like Sets, Lists, and Maps. When every element of the Set is suppressed in custom diff, // it returns true for d.HasChange anyway (it returns false for suppressed changes on primitive types like Number, Bool, String, etc.). + // TODO [SNOW-1648997]: address the above comment ComputedIfAnyAttributeChanged( networkPolicySchema, ShowOutputAttributeName, diff --git a/pkg/resources/user.go b/pkg/resources/user.go index eebb3dbf5e..89c240894a 100644 --- a/pkg/resources/user.go +++ b/pkg/resources/user.go @@ -20,8 +20,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -// TODO [SNOW-1348101 - next PR]: update all changes in README -// TODO [SNOW-1348101 - next PR]: add IgnoreChangeToCurrentSnowflakeValueInShow and other suppressors var userSchema = map[string]*schema.Schema{ "name": { Type: schema.TypeString, @@ -36,18 +34,19 @@ var userSchema = map[string]*schema.Schema{ Description: "Password for the user. **WARNING:** this will put the password in the terraform state file. Use carefully.", }, "login_name": { - Type: schema.TypeString, - Optional: true, - Sensitive: true, - Description: "The name users use to log in. If not supplied, snowflake will use name instead. Login names are always case-insensitive.", + Type: schema.TypeString, + Optional: true, + Sensitive: true, + DiffSuppressFunc: SuppressIfAny(ignoreCaseSuppressFunc, IgnoreChangeToCurrentSnowflakeValueInShow("login_name")), + Description: "The name users use to log in. If not supplied, snowflake will use name instead. Login names are always case-insensitive.", // login_name is case-insensitive - DiffSuppressFunc: ignoreCaseSuppressFunc, }, // TODO [SNOW-1348101 - next PR]: handle external changes and the default behavior correctly; same with the login_name "display_name": { - Type: schema.TypeString, - Optional: true, - Description: "Name displayed for the user in the Snowflake web interface.", + Type: schema.TypeString, + Optional: true, + DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValueInShow("display_name"), + Description: "Name displayed for the user in the Snowflake web interface.", }, "first_name": { Type: schema.TypeString, @@ -77,6 +76,7 @@ var userSchema = map[string]*schema.Schema{ Type: schema.TypeString, Optional: true, ValidateDiagFunc: validateBooleanString, + DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValueInShow("must_change_password"), Description: booleanStringFieldDescription("Specifies whether the user is forced to change their password on next login (including their first/initial login) into the system."), Default: BooleanDefault, }, @@ -84,21 +84,22 @@ var userSchema = map[string]*schema.Schema{ Type: schema.TypeString, Optional: true, ValidateDiagFunc: validateBooleanString, + DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValueInShow("disabled"), Description: booleanStringFieldDescription("Specifies whether the user is disabled, which prevents logging in and aborts all the currently-running queries for the user."), Default: BooleanDefault, }, - // TODO [SNOW-1348101 - next PR]: consider handling external change to 0 or from 0? + // TODO [SNOW-1649000]: consider handling external change if there is no config (or zero) for `days_to_expiry` and other similar attributes (what about this the other way around?) "days_to_expiry": { Type: schema.TypeInt, Optional: true, - Description: "Specifies the number of days after which the user status is set to `Expired` and the user is no longer allowed to log in. This is useful for defining temporary users (i.e. users who should only have access to Snowflake for a limited time period). In general, you should not set this property for [account administrators](https://docs.snowflake.com/en/user-guide/security-access-control-considerations.html#label-accountadmin-users) (i.e. users with the `ACCOUNTADMIN` role) because Snowflake locks them out when they become `Expired`.", + Description: externalChangesNotDetectedFieldDescription("Specifies the number of days after which the user status is set to `Expired` and the user is no longer allowed to log in. This is useful for defining temporary users (i.e. users who should only have access to Snowflake for a limited time period). In general, you should not set this property for [account administrators](https://docs.snowflake.com/en/user-guide/security-access-control-considerations.html#label-accountadmin-users) (i.e. users with the `ACCOUNTADMIN` role) because Snowflake locks them out when they become `Expired`."), }, "mins_to_unlock": { Type: schema.TypeInt, Optional: true, ValidateFunc: validation.IntAtLeast(0), Default: IntDefault, - Description: "Specifies the number of minutes until the temporary lock on the user login is cleared. To protect against unauthorized user login, Snowflake places a temporary lock on a user after five consecutive unsuccessful login attempts. When creating a user, this property can be set to prevent them from logging in until the specified amount of time passes. To remove a lock immediately for a user, specify a value of 0 for this parameter.", + Description: externalChangesNotDetectedFieldDescription("Specifies the number of minutes until the temporary lock on the user login is cleared. To protect against unauthorized user login, Snowflake places a temporary lock on a user after five consecutive unsuccessful login attempts. When creating a user, this property can be set to prevent them from logging in until the specified amount of time passes. To remove a lock immediately for a user, specify a value of 0 for this parameter. **Note** because this value changes continuously after setting it, the provider is currently NOT handling the external changes to it."), }, "default_warehouse": { Type: schema.TypeString, @@ -110,7 +111,7 @@ var userSchema = map[string]*schema.Schema{ "default_namespace": { Type: schema.TypeString, Optional: true, - DiffSuppressFunc: suppressIdentifierQuoting, + DiffSuppressFunc: SuppressIfAny(suppressIdentifierQuoting, IgnoreChangeToCurrentSnowflakeValueInShow("default_namespace")), Description: "Specifies the namespace (database only or database and schema) that is active by default for the user’s session upon login. Note that the CREATE USER operation does not verify that the namespace exists.", }, "default_role": { @@ -131,13 +132,12 @@ var userSchema = map[string]*schema.Schema{ Optional: true, Description: "Specifies the set of secondary roles that are active for the user’s session upon login. Currently only [\"ALL\"] value is supported - more information can be found in [doc](https://docs.snowflake.com/en/sql-reference/sql/create-user#optional-object-properties-objectproperties).", }, - // TODO [SNOW-1348101 - next PR]: note that external changes are not handled (and with other params that this is true) "mins_to_bypass_mfa": { Type: schema.TypeInt, Optional: true, ValidateFunc: validation.IntAtLeast(0), Default: IntDefault, - Description: "Specifies the number of minutes to temporarily bypass MFA for the user. This property can be used to allow a MFA-enrolled user to temporarily bypass MFA during login in the event that their MFA device is not available.", + Description: externalChangesNotDetectedFieldDescription("Specifies the number of minutes to temporarily bypass MFA for the user. This property can be used to allow a MFA-enrolled user to temporarily bypass MFA during login in the event that their MFA device is not available."), }, "rsa_public_key": { Type: schema.TypeString, @@ -158,7 +158,7 @@ var userSchema = map[string]*schema.Schema{ Type: schema.TypeString, Optional: true, ValidateDiagFunc: validateBooleanString, - Description: booleanStringFieldDescription("Allows enabling or disabling [multi-factor authentication](https://docs.snowflake.com/en/user-guide/security-mfa)."), + Description: externalChangesNotDetectedFieldDescription(booleanStringFieldDescription("Allows enabling or disabling [multi-factor authentication](https://docs.snowflake.com/en/user-guide/security-mfa).")), Default: BooleanDefault, }, "user_type": { @@ -200,8 +200,8 @@ func User() *schema.Resource { CustomizeDiff: customdiff.All( // TODO [SNOW-1629468 - next pr]: test "default_role", "default_secondary_roles" - // TODO [SNOW-TODO]: "default_secondary_roles" have to stay commented out because of how the SDKv2 handles diff suppressions and custom diffs for sets - ComputedIfAnyAttributeChanged(userSchema, ShowOutputAttributeName, "password", "login_name", "display_name", "first_name", "middle_name", "last_name", "email", "must_change_password", "disabled", "days_to_expiry", "mins_to_unlock", "default_warehouse", "default_namespace", "default_role", "mins_to_bypass_mfa", "rsa_public_key", "rsa_public_key_2", "comment", "disable_mfa"), + // TODO [SNOW-1648997]: "default_secondary_roles" have to stay commented out because of how the SDKv2 handles diff suppressions and custom diffs for sets + ComputedIfAnyAttributeChanged(userSchema, ShowOutputAttributeName, "password", "login_name", "display_name", "first_name", "last_name", "email", "must_change_password", "disabled", "days_to_expiry", "mins_to_unlock", "default_warehouse", "default_namespace", "default_role", "mins_to_bypass_mfa", "rsa_public_key", "rsa_public_key_2", "comment", "disable_mfa"), ComputedIfAnyAttributeChanged(userParametersSchema, ParametersAttributeName, collections.Map(sdk.AsStringList(sdk.AllUserParameters), strings.ToLower)...), ComputedIfAnyAttributeChanged(userSchema, FullyQualifiedNameAttributeName, "name"), userParametersCustomDiff, diff --git a/pkg/resources/user_acceptance_test.go b/pkg/resources/user_acceptance_test.go index 2cef1cea23..b93585654d 100644 --- a/pkg/resources/user_acceptance_test.go +++ b/pkg/resources/user_acceptance_test.go @@ -69,27 +69,29 @@ func TestAcc_User_BasicFlows(t *testing.T) { WithComment(comment). WithDisableMfa("true") - userModelAllAttributesChanged := model.User("w", id.Name()). - WithPassword(newPass). - WithLoginName(id.Name() + "_other_login"). - WithDisplayName("New Display Name"). - WithFirstName("Janek"). - WithMiddleName("Kuba"). - WithLastName("Terraformowski"). - WithEmail("fake@email.net"). - WithMustChangePassword("false"). - WithDisabled("true"). - WithDaysToExpiry(12). - WithMinsToUnlock(13). - WithDefaultWarehouse("other_warehouse"). - WithDefaultNamespace("one_part_namespace"). - WithDefaultRole("other_role"). - WithDefaultSecondaryRolesStringList("ALL"). - WithMinsToBypassMfa(14). - WithRsaPublicKey(key2). - WithRsaPublicKey2(key1). - WithComment(newComment). - WithDisableMfa("false") + userModelAllAttributesChanged := func(loginName string) *model.UserModel { + return model.User("w", id.Name()). + WithPassword(newPass). + WithLoginName(loginName). + WithDisplayName("New Display Name"). + WithFirstName("Janek"). + WithMiddleName("Kuba"). + WithLastName("Terraformowski"). + WithEmail("fake@email.net"). + WithMustChangePassword("false"). + WithDisabled("true"). + WithDaysToExpiry(12). + WithMinsToUnlock(13). + WithDefaultWarehouse("other_warehouse"). + WithDefaultNamespace("one_part_namespace"). + WithDefaultRole("other_role"). + WithDefaultSecondaryRolesStringList("ALL"). + WithMinsToBypassMfa(14). + WithRsaPublicKey(key2). + WithRsaPublicKey2(key1). + WithComment(newComment). + WithDisableMfa("false") + } resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, @@ -194,9 +196,9 @@ func TestAcc_User_BasicFlows(t *testing.T) { }, // CHANGE PROPERTIES { - Config: config.FromModel(t, userModelAllAttributesChanged), + Config: config.FromModel(t, userModelAllAttributesChanged(id.Name()+"_other_login")), Check: assert.AssertThat(t, - resourceassert.UserResource(t, userModelAllAttributesChanged.ResourceReference()). + resourceassert.UserResource(t, userModelAllAttributesChanged(id.Name()+"_other_login").ResourceReference()). HasNameString(id.Name()). HasPasswordString(newPass). HasLoginNameString(fmt.Sprintf("%s_other_login", id.Name())). @@ -223,7 +225,7 @@ func TestAcc_User_BasicFlows(t *testing.T) { }, // IMPORT { - ResourceName: userModelAllAttributesChanged.ResourceReference(), + ResourceName: userModelAllAttributesChanged(id.Name() + "_other_login").ResourceReference(), ImportState: true, ImportStateVerify: true, ImportStateVerifyIgnore: []string{"password", "disable_mfa", "days_to_expiry", "mins_to_unlock", "mins_to_bypass_mfa", "default_namespace", "login_name", "show_output.0.days_to_expiry"}, @@ -233,6 +235,18 @@ func TestAcc_User_BasicFlows(t *testing.T) { HasLoginNameString(fmt.Sprintf("%s_OTHER_LOGIN", id.Name())), ), }, + // CHANGE PROP TO THE CURRENT SNOWFLAKE VALUE + { + PreConfig: func() { + acc.TestClient().User.SetLoginName(t, id, id.Name()+"_different_login") + }, + Config: config.FromModel(t, userModelAllAttributesChanged(id.Name()+"_different_login")), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, + }, // UNSET ALL { Config: config.FromModel(t, userModelNoAttributes), diff --git a/pkg/resources/user_public_keys_acceptance_test.go b/pkg/resources/user_public_keys_acceptance_test.go index c6e15c9f54..aea2969f5f 100644 --- a/pkg/resources/user_public_keys_acceptance_test.go +++ b/pkg/resources/user_public_keys_acceptance_test.go @@ -12,7 +12,6 @@ import ( "github.com/hashicorp/terraform-plugin-testing/tfversion" ) -// TODO [SNOW-1348101 - next PR]: change description of user public keys resource (should be used only if user is not managed by terraform) func TestAcc_UserPublicKeys(t *testing.T) { userId := acc.TestClient().Ids.RandomAccountObjectIdentifier() key1, _ := random.GenerateRSAPublicKey(t) diff --git a/pkg/sdk/testint/users_integration_test.go b/pkg/sdk/testint/users_integration_test.go index 759ea08189..dcba7985b0 100644 --- a/pkg/sdk/testint/users_integration_test.go +++ b/pkg/sdk/testint/users_integration_test.go @@ -354,7 +354,6 @@ func TestInt_Users(t *testing.T) { assert.Equal(t, 0, *userDetails.MinsToBypassMfa.Value) }) - // TODO [SNOW-1348101 - next PR]: consult this with appropriate team when we have all the problems listed t.Run("create and alter: problems with public key fingerprints", func(t *testing.T) { id := testClientHelper().Ids.RandomAccountObjectIdentifier() diff --git a/templates/resources/user.md.tmpl b/templates/resources/user.md.tmpl index e3e231663e..cd2d0b52e5 100644 --- a/templates/resources/user.md.tmpl +++ b/templates/resources/user.md.tmpl @@ -11,6 +11,14 @@ description: |- !> **V1 release candidate** This resource was reworked and is a release candidate for the V1. We do not expect significant changes in it before the V1. We will welcome any feedback and adjust the resource if needed. Any errors reported will be resolved with a higher priority. We encourage checking this resource out before the V1 release. Please follow the [migration guide](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/MIGRATION_GUIDE.md#v094x--v0950) to use it. +-> **Note** `snowflake_user_password_policy_attachment` will be reworked in the following versions of the provider which may still affect this resource. + +-> **Note** Attaching user policies will be handled in the following versions of the provider which may still affect this resource. + +-> **Note** `service` and `legacy_service` user types are currently not supported. They will be supported in the following versions as separate resources (namely `snowflake_service_user` and `snowflake_legacy_service_user`). + +-> **Note** External changes to `days_to_expiry`, `mins_to_unlock`, and `mins_to_bypass_mfa` are not currently handled by the provider (because the value changes continuously on Snowflake side after setting it). + # {{.Name}} ({{.Type}}) {{ .Description | trimspace }} diff --git a/templates/resources/user_public_keys.md.tmpl b/templates/resources/user_public_keys.md.tmpl new file mode 100644 index 0000000000..525a4eeb2d --- /dev/null +++ b/templates/resources/user_public_keys.md.tmpl @@ -0,0 +1,35 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ if gt (len (split .Description "")) 1 -}} +{{ index (split .Description "") 1 | plainmarkdown | trimspace | prefixlines " " }} +{{- else -}} +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +{{- end }} +--- + +!> **Important** Starting from v0.95.0, it is advised to use this resource **only** if users are not managed through terraform. Check more in the [migration guide](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/MIGRATION_GUIDE.md#v094x--v0950). + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +{{ if .HasExample -}} +## Example Usage + +{{ tffile (printf "examples/resources/%s/resource.tf" .Name)}} +-> **Note** Instead of using fully_qualified_name, you can reference objects managed outside Terraform by constructing a correct ID, consult [identifiers guide](https://registry.terraform.io/providers/Snowflake-Labs/snowflake/latest/docs/guides/identifiers#new-computed-fully-qualified-name-field-in-resources). + + +{{- end }} + +{{ .SchemaMarkdown | trimspace }} +{{- if .HasImport }} + +## Import + +Import is supported using the following syntax: + +{{ codefile "shell" (printf "examples/resources/%s/import.sh" .Name)}} +{{- end }}