From 114cf3d01aa3b50aff562a302674feeb7f911fec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Thu, 27 Jun 2024 14:15:48 +0200 Subject: [PATCH 01/14] Apply new resource conventions to scim integration --- MIGRATION_GUIDE.md | 4 + docs/resources/scim_integration.md | 84 ++++- pkg/resources/custom_diffs.go | 20 -- pkg/resources/diff_suppressions.go | 36 +- pkg/resources/scim_integration.go | 317 ++++++++++++------ .../scim_integration_acceptance_test.go | 31 +- .../scim_integration_state_upgraders.go | 2 +- pkg/resources/warehouse.go | 22 +- .../warehouse_rework_show_output_proposal.go | 49 ++- pkg/schemas/common_types.go | 42 +++ pkg/schemas/parameters.go | 12 - pkg/schemas/security_integration.go | 60 ++++ 12 files changed, 504 insertions(+), 175 deletions(-) create mode 100644 pkg/schemas/common_types.go delete mode 100644 pkg/schemas/parameters.go create mode 100644 pkg/schemas/security_integration.go diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index 2ceb3780e4..4d8b947144 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -9,6 +9,10 @@ across different versions. Following the [announcement](https://github.com/Snowflake-Labs/terraform-provider-snowflake/discussions/2736) we have removed the old grant resources. The two resources [snowflake_role_ownership_grant](https://registry.terraform.io/providers/Snowflake-Labs/snowflake/latest/docs/resources/role_ownership_grant) and [snowflake_user_ownership_grant](https://registry.terraform.io/providers/Snowflake-Labs/snowflake/latest/docs/resources/user_ownership_grant) were not listed in the announcement, but they were also marked as deprecated ones. We are removing them too to conclude the grants redesign saga. ### snowflake_scim_integration resource changes +#### *(behavior change)* Changed behavior of `sync_password` + +Now, the `sync_password` field will set the state value to `unknown` whenever the value is not set in the config. This indicates that the value on the Snowflake side is set to the Snowflake default. + #### *(behavior change)* Renamed fields Renamed field `provisioner_role` to `run_as_role` to align with Snowflake docs. Please rename this field in your configuration files. State will be migrated automatically. diff --git a/docs/resources/scim_integration.md b/docs/resources/scim_integration.md index f9d68cbb94..20420b5aec 100644 --- a/docs/resources/scim_integration.md +++ b/docs/resources/scim_integration.md @@ -45,12 +45,92 @@ resource "snowflake_scim_integration" "test" { - `comment` (String) Specifies a comment for the integration. - `network_policy` (String) Specifies an existing network policy that controls SCIM network traffic. -- `sync_password` (Boolean) Specifies whether to enable or disable the synchronization of a user password from an Okta SCIM client as part of the API request to Snowflake. +- `sync_password` (String) Specifies whether to enable or disable the synchronization of a user password from an Okta SCIM client as part of the API request to Snowflake. Available options are: `true` or `false`. When the value is not set in the configuration the provider will put `unknown` there which means to use the Snowflake default for this value. ### Read-Only -- `created_on` (String) Date and time when the SCIM integration was created. +- `describe_output` (List of Object) Outputs the result of `SHOW SECURITY INTEGRATIONS` for the given security integration. (see [below for nested schema](#nestedatt--describe_output)) - `id` (String) The ID of this resource. +- `show_output` (List of Object) Outputs the result of `SHOW SECURITY INTEGRATIONS` for the given security integration. (see [below for nested schema](#nestedatt--show_output)) + + +### Nested Schema for `describe_output` + +Read-Only: + +- `comment` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--comment)) +- `enabled` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--enabled)) +- `network_policy` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--network_policy)) +- `run_as_role` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--run_as_role)) +- `sync_password` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--sync_password)) + + +### Nested Schema for `describe_output.comment` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.enabled` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.network_policy` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.run_as_role` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.sync_password` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + + +### Nested Schema for `show_output` + +Read-Only: + +- `category` (String) +- `comment` (String) +- `created_on` (String) +- `enabled` (Boolean) +- `integration_type` (String) +- `name` (String) ## Import diff --git a/pkg/resources/custom_diffs.go b/pkg/resources/custom_diffs.go index c3e417e523..14382da0af 100644 --- a/pkg/resources/custom_diffs.go +++ b/pkg/resources/custom_diffs.go @@ -9,8 +9,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -61,24 +59,6 @@ func ParameterValueComputedIf(key string, parameters []*sdk.Parameter, objectPar } } -func BoolComputedIf(key string, getDefault func(client *sdk.Client, id sdk.AccountObjectIdentifier) (string, error)) schema.CustomizeDiffFunc { - return customdiff.ComputedIf(key, func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) bool { - configValue := d.GetRawConfig().AsValueMap()[key] - if !configValue.IsNull() { - return false - } - - client := meta.(*provider.Context).Client - - def, err := getDefault(client, helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier)) - if err != nil { - return false - } - stateValue := d.Get(key).(bool) - return def != strconv.FormatBool(stateValue) - }) -} - // TODO [follow-up PR]: test func ComputedIfAnyAttributeChanged(key string, changedAttributeKeys ...string) schema.CustomizeDiffFunc { return customdiff.ComputedIf(key, func(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) bool { diff --git a/pkg/resources/diff_suppressions.go b/pkg/resources/diff_suppressions.go index d2de195517..59edce5c83 100644 --- a/pkg/resources/diff_suppressions.go +++ b/pkg/resources/diff_suppressions.go @@ -27,17 +27,17 @@ func IgnoreAfterCreation(_, _, _ string, d *schema.ResourceData) bool { return d.Id() != "" } -func IgnoreChangeToCurrentSnowflakeValue(keyInShowOutput string) schema.SchemaDiffSuppressFunc { +func IgnoreChangeToCurrentSnowflakeValueInShow(keyInShowOutput string) schema.SchemaDiffSuppressFunc { return func(_, _, new string, d *schema.ResourceData) bool { if d.Id() == "" { return false } - if showOutput, ok := d.GetOk(showOutputAttributeName); ok { - showOutputList := showOutput.([]any) - if len(showOutputList) == 1 { - result := showOutputList[0].(map[string]any) - log.Printf("[DEBUG] IgnoreChangeToCurrentSnowflakeValue: value for key %s is %v, new value is %s, comparison result is: %t", keyInShowOutput, result[keyInShowOutput], new, new == fmt.Sprintf("%v", result[keyInShowOutput])) + if queryOutput, ok := d.GetOk(showOutputAttributeName); ok { + queryOutputList := queryOutput.([]any) + if len(queryOutputList) == 1 { + result := queryOutputList[0].(map[string]any) + log.Printf("[DEBUG] IgnoreChangeToCurrentSnowflakeValueInShow: value for key %s is %v, new value is %s, comparison result is: %t", keyInShowOutput, result[keyInShowOutput], new, new == fmt.Sprintf("%v", result[keyInShowOutput])) if new == fmt.Sprintf("%v", result[keyInShowOutput]) { return true } @@ -47,6 +47,30 @@ func IgnoreChangeToCurrentSnowflakeValue(keyInShowOutput string) schema.SchemaDi } } +func IgnoreChangeToCurrentSnowflakeValueInDescribe(keyInDescribeOutput string) schema.SchemaDiffSuppressFunc { + return func(_, _, new string, d *schema.ResourceData) bool { + if d.Id() == "" { + return false + } + + if queryOutput, ok := d.GetOk(describeOutputAttributeName); ok { + queryOutputList := queryOutput.([]any) + if len(queryOutputList) == 1 { + result := queryOutputList[0].(map[string]any) + newValueInDescribeList := result[keyInDescribeOutput].([]any) + if len(newValueInDescribeList) == 1 { + newValueInDescribe := newValueInDescribeList[0].(map[string]any)["value"] + log.Printf("[DEBUG] IgnoreChangeToCurrentSnowflakeValueInDescribe: value for key %s is %v, new value is %s, comparison result is: %t", keyInDescribeOutput, newValueInDescribe, new, new == fmt.Sprintf("%v", newValueInDescribe)) + if new == fmt.Sprintf("%v", newValueInDescribe) { + return true + } + } + } + } + return false + } +} + func SuppressIfAny(diffSuppressFunctions ...schema.SchemaDiffSuppressFunc) schema.SchemaDiffSuppressFunc { return func(k, old, new string, d *schema.ResourceData) bool { var suppress bool diff --git a/pkg/resources/scim_integration.go b/pkg/resources/scim_integration.go index b6d83b3ece..594af22180 100644 --- a/pkg/resources/scim_integration.go +++ b/pkg/resources/scim_integration.go @@ -4,16 +4,20 @@ import ( "context" "errors" "fmt" - "log" + "strconv" "strings" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/logging" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/schemas" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) @@ -26,12 +30,10 @@ var scimIntegrationSchema = map[string]*schema.Schema{ Description: "String that specifies the identifier (i.e. name) for the integration; must be unique in your account.", }, "enabled": { - Type: schema.TypeBool, - Required: true, - Description: "Specify whether the security integration is enabled. ", - DiffSuppressFunc: func(k, oldValue, newValue string, d *schema.ResourceData) bool { - return d.GetRawConfig().AsValueMap()["enabled"].IsNull() - }, + Type: schema.TypeBool, + Required: true, + Description: "Specify whether the security integration is enabled.", + DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValueInDescribe("enabled"), }, "scim_client": { Type: schema.TypeString, @@ -60,25 +62,36 @@ var scimIntegrationSchema = map[string]*schema.Schema{ ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), Optional: true, Description: "Specifies an existing network policy that controls SCIM network traffic.", - DiffSuppressFunc: func(_, old, new string, d *schema.ResourceData) bool { - return sdk.NewAccountObjectIdentifierFromFullyQualifiedName(old) == sdk.NewAccountObjectIdentifierFromFullyQualifiedName(new) - }, + DiffSuppressFunc: SuppressIfAny(suppressIdentifierQuoting, IgnoreChangeToCurrentSnowflakeValueInDescribe("network_policy")), }, "sync_password": { - Type: schema.TypeBool, - Optional: true, - Computed: true, - Description: "Specifies whether to enable or disable the synchronization of a user password from an Okta SCIM client as part of the API request to Snowflake.", + Type: schema.TypeString, + Optional: true, + Default: "unknown", + ValidateFunc: validation.StringInSlice([]string{"true", "false"}, true), + DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValueInDescribe("sync_password"), + Description: "Specifies whether to enable or disable the synchronization of a user password from an Okta SCIM client as part of the API request to Snowflake. Available options are: `true` or `false`. When the value is not set in the configuration the provider will put `unknown` there which means to use the Snowflake default for this value.", }, "comment": { Type: schema.TypeString, Optional: true, Description: "Specifies a comment for the integration.", }, - "created_on": { - Type: schema.TypeString, + showOutputAttributeName: { + Type: schema.TypeList, Computed: true, - Description: "Date and time when the SCIM integration was created.", + Description: "Outputs the result of `SHOW SECURITY INTEGRATIONS` for the given security integration.", + Elem: &schema.Resource{ + Schema: schemas.ShowSecurityIntegrationSchema, + }, + }, + describeOutputAttributeName: { + Type: schema.TypeList, + Computed: true, + Description: "Outputs the result of `SHOW SECURITY INTEGRATIONS` for the given security integration.", + Elem: &schema.Resource{ + Schema: schemas.DescribeSecurityIntegrationSchema, + }, }, } @@ -87,27 +100,20 @@ func SCIMIntegration() *schema.Resource { SchemaVersion: 1, CreateContext: CreateContextSCIMIntegration, - ReadContext: ReadContextSCIMIntegration, + ReadContext: ReadContextSCIMIntegration(true), UpdateContext: UpdateContextSCIMIntegration, DeleteContext: DeleteContextSCIMIntegration, - CustomizeDiff: customdiff.All( - BoolComputedIf("sync_password", func(client *sdk.Client, id sdk.AccountObjectIdentifier) (string, error) { - props, err := client.SecurityIntegrations.Describe(context.Background(), id) - if err != nil { - return "", err - } - for _, prop := range props { - if prop.GetName() == "SYNC_PASSWORD" { - return prop.GetDefault(), nil - } - } - return "", fmt.Errorf("") - }), - ), + Schema: scimIntegrationSchema, Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, + StateContext: ImportScimIntegration, }, + + CustomizeDiff: customdiff.All( + ComputedIfAnyAttributeChanged(showOutputAttributeName, schemas.ShowSecurityIntegrationSchemaKeys...), + ComputedIfAnyAttributeChanged(showOutputAttributeName, schemas.DescribeSecurityIntegrationSchemaKeys...), + ), + StateUpgraders: []schema.StateUpgrader{ { Version: 0, @@ -119,152 +125,243 @@ func SCIMIntegration() *schema.Resource { } } +func ImportScimIntegration(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { + logging.DebugLogger.Printf("[DEBUG] Starting scim integration import") + client := meta.(*provider.Context).Client + id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) + + integration, err := client.SecurityIntegrations.ShowByID(ctx, id) + if err != nil { + return nil, err + } + + integrationProperties, err := client.SecurityIntegrations.Describe(ctx, id) + if err != nil { + return nil, err + } + + if err = d.Set("name", integration.Name); err != nil { + return nil, err + } + if err = d.Set("enabled", integration.Enabled); err != nil { + return nil, err + } + if scimClient, err := integration.SubType(); err == nil { + if err = d.Set("scim_client", scimClient); err != nil { + return nil, err + } + } + if runAsRoleProperty, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "RUN_AS_ROLE" }); err == nil { + if err = d.Set("run_as_role", runAsRoleProperty.Value); err != nil { + return nil, err + } + } + if networkPolicyProperty, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "NETWORK_POLICY" }); err == nil { + if err = d.Set("network_policy", networkPolicyProperty.Value); err != nil { + return nil, err + } + } + if syncPasswordProperty, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "SYNC_PASSWORD" }); err == nil { + if err = d.Set("sync_password", syncPasswordProperty.Value); err != nil { + return nil, err + } + } + if err = d.Set("comment", integration.Comment); err != nil { + return nil, err + } + + return []*schema.ResourceData{d}, nil +} + func CreateContextSCIMIntegration(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*provider.Context).Client - name := d.Get("name").(string) - id := sdk.NewAccountObjectIdentifier(name) - scimClientRaw := d.Get("scim_client").(string) - runAsRoleRaw := d.Get("run_as_role").(string) - scimClient, err := sdk.ToScimSecurityIntegrationScimClientOption(scimClientRaw) + + id := sdk.NewAccountObjectIdentifier(d.Get("name").(string)) + + scimClient, err := sdk.ToScimSecurityIntegrationScimClientOption(d.Get("scim_client").(string)) if err != nil { return diag.FromErr(err) } - runAsRole, err := sdk.ToScimSecurityIntegrationRunAsRoleOption(runAsRoleRaw) + + runAsRole, err := sdk.ToScimSecurityIntegrationRunAsRoleOption(d.Get("run_as_role").(string)) if err != nil { return diag.FromErr(err) } + req := sdk.NewCreateScimSecurityIntegrationRequest(id, scimClient, runAsRole).WithEnabled(d.Get("enabled").(bool)) if v, ok := d.GetOk("network_policy"); ok { req.WithNetworkPolicy(sdk.NewAccountObjectIdentifier(v.(string))) } - if !d.GetRawConfig().AsValueMap()["sync_password"].IsNull() { - req.WithSyncPassword(d.Get("sync_password").(bool)) + + if v := d.Get("sync_password").(string); v != "unknown" { + parsed, err := strconv.ParseBool(v) + if err != nil { + return diag.FromErr(err) + } + + req.WithSyncPassword(parsed) } + if v, ok := d.GetOk("comment"); ok { req.WithComment(v.(string)) } + if err := client.SecurityIntegrations.CreateScim(ctx, req); err != nil { return diag.FromErr(err) } d.SetId(helpers.EncodeSnowflakeID(id)) - return ReadContextSCIMIntegration(ctx, d, meta) + return ReadContextSCIMIntegration(false)(ctx, d, meta) } -func ReadContextSCIMIntegration(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*provider.Context).Client - id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) +func ReadContextSCIMIntegration(withExternalChangesMarking bool) schema.ReadContextFunc { + return func(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + client := meta.(*provider.Context).Client + id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) - integration, err := client.SecurityIntegrations.ShowByID(ctx, id) - if err != nil { - if errors.Is(err, sdk.ErrObjectNotFound) { - d.SetId("") - return diag.Diagnostics{ - diag.Diagnostic{ - Severity: diag.Warning, - Summary: "Failed to query security integration. Marking the resource as removed.", - Detail: fmt.Sprintf("Security integration name: %s, Err: %s", id.FullyQualifiedName(), err), - }, + integration, err := client.SecurityIntegrations.ShowByID(ctx, id) + if err != nil { + if errors.Is(err, sdk.ErrObjectNotFound) { + d.SetId("") + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Warning, + Summary: "Failed to query security integration. Marking the resource as removed.", + Detail: fmt.Sprintf("Security integration name: %s, Err: %s", id.FullyQualifiedName(), err), + }, + } } + return diag.FromErr(err) } - return diag.FromErr(err) - } - - if c := integration.Category; c != sdk.SecurityIntegrationCategory { - return diag.FromErr(fmt.Errorf("expected %v to be a SECURITY integration, got %v", id, c)) - } - if err := d.Set("name", integration.Name); err != nil { - return diag.FromErr(err) - } + integrationProperties, err := client.SecurityIntegrations.Describe(ctx, id) + if err != nil { + if errors.Is(err, sdk.ErrObjectNotFound) { + d.SetId("") + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Warning, + Summary: "Failed to query security integration properties. Marking the resource as removed.", + Detail: fmt.Sprintf("Security integration name: %s, Err: %s", id.FullyQualifiedName(), err), + }, + } + } + return diag.FromErr(err) + } - if err := d.Set("comment", integration.Comment); err != nil { - return diag.FromErr(err) - } + if c := integration.Category; c != sdk.SecurityIntegrationCategory { + return diag.FromErr(fmt.Errorf("expected %v to be a SECURITY integration, got %v", id, c)) + } - if err := d.Set("created_on", integration.CreatedOn.String()); err != nil { - return diag.FromErr(err) - } + if withExternalChangesMarking { + scimClient, err := integration.SubType() + if err != nil { + return diag.FromErr(err) + } - if err := d.Set("enabled", integration.Enabled); err != nil { - return diag.FromErr(err) - } + if err = handleExternalChangesToObjectInShow(d, + showMapping{"comment", "comment", integration.Comment, integration.Comment, nil}, + showMapping{"type", "scim_client", integration.IntegrationType, scimClient, nil}, + ); err != nil { + return diag.FromErr(err) + } - scimClient, err := integration.SubType() - if err != nil { - return diag.FromErr(err) - } - if err := d.Set("scim_client", scimClient); err != nil { - return diag.FromErr(err) - } - integrationProperties, err := client.SecurityIntegrations.Describe(ctx, id) - if err != nil { - return diag.FromErr(err) - } - for _, property := range integrationProperties { - name := property.Name - value := property.Value - switch name { - case "ENABLED", "COMMENT": - // We set this using the SHOW INTEGRATION call so let's ignore it here - case "NETWORK_POLICY": - networkPolicyID := sdk.NewAccountObjectIdentifier(value) - if err := d.Set("network_policy", networkPolicyID.FullyQualifiedName()); err != nil { + networkPolicyProperty, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "NETWORK_POLICY" }) + if err != nil { return diag.FromErr(err) } - case "SYNC_PASSWORD": - if err := d.Set("sync_password", helpers.StringToBool(value)); err != nil { + + syncPasswordProperty, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "SYNC_PASSWORD" }) + if err != nil { return diag.FromErr(err) } - case "RUN_AS_ROLE": - if err := d.Set("run_as_role", value); err != nil { + + if err = handleExternalChangesToObjectInDescribe(d, + describeMapping{"network_policy", "network_policy", networkPolicyProperty.Value, networkPolicyProperty.Value, nil}, + describeMapping{"sync_password", "sync_password", syncPasswordProperty.Value, syncPasswordProperty.Value, nil}, + ); err != nil { return diag.FromErr(err) } - default: - log.Printf("[WARN] unexpected property %v returned from Snowflake", name) } - } - return nil + if !d.GetRawConfig().IsNull() { + if v := d.GetRawConfig().AsValueMap()["network_policy"]; !v.IsNull() { + if err = d.Set("network_policy", v.AsString()); err != nil { + return diag.FromErr(err) + } + } + if v := d.GetRawConfig().AsValueMap()["sync_password"]; !v.IsNull() { + if err = d.Set("sync_password", v.AsString()); err != nil { + return diag.FromErr(err) + } + } + if v := d.GetRawConfig().AsValueMap()["comment"]; !v.IsNull() { + if err = d.Set("comment", v.AsString()); err != nil { + return diag.FromErr(err) + } + } + } + + if err = d.Set(showOutputAttributeName, []map[string]any{schemas.SecurityIntegrationToSchema(integration)}); err != nil { + return diag.FromErr(err) + } + + if err = d.Set(describeOutputAttributeName, []map[string]any{schemas.SecurityIntegrationPropertiesToSchema(integrationProperties)}); err != nil { + return diag.FromErr(err) + } + + return nil + } } func UpdateContextSCIMIntegration(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*provider.Context).Client id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) set, unset := sdk.NewScimIntegrationSetRequest(), sdk.NewScimIntegrationUnsetRequest() + if d.HasChange("enabled") { set.WithEnabled(d.Get("enabled").(bool)) } + if d.HasChange("network_policy") { - networkPolicyID := sdk.NewAccountObjectIdentifier(d.Get("network_policy").(string)) - if networkPolicyID.Name() != "" { - set.WithNetworkPolicy(networkPolicyID) + if v := d.Get("network_policy").(string); v != "" { + set.WithNetworkPolicy(sdk.NewAccountObjectIdentifier(v)) } else { unset.WithNetworkPolicy(true) } } - if !d.GetRawConfig().AsValueMap()["sync_password"].IsNull() { - set.WithSyncPassword(d.Get("sync_password").(bool)) - } else { - unset.WithSyncPassword(true) + + if d.HasChange("sync_password") { + if v := d.Get("sync_password").(string); v != "unknown" { + parsed, err := strconv.ParseBool(v) + if err != nil { + return diag.FromErr(err) + } + set.WithSyncPassword(parsed) + } else { + unset.WithSyncPassword(true) + } } if d.HasChange("comment") { set.WithComment(sdk.StringAllowEmpty{Value: d.Get("comment").(string)}) } + if (*set != sdk.ScimIntegrationSetRequest{}) { if err := client.SecurityIntegrations.AlterScim(ctx, sdk.NewAlterScimSecurityIntegrationRequest(id).WithSet(*set)); err != nil { return diag.FromErr(err) } } + if (*unset != sdk.ScimIntegrationUnsetRequest{}) { if err := client.SecurityIntegrations.AlterScim(ctx, sdk.NewAlterScimSecurityIntegrationRequest(id).WithUnset(*unset)); err != nil { return diag.FromErr(err) } } - return ReadContextSCIMIntegration(ctx, d, meta) + + return ReadContextSCIMIntegration(false)(ctx, d, meta) } func DeleteContextSCIMIntegration(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { diff --git a/pkg/resources/scim_integration_acceptance_test.go b/pkg/resources/scim_integration_acceptance_test.go index f175a453cb..f111b234a9 100644 --- a/pkg/resources/scim_integration_acceptance_test.go +++ b/pkg/resources/scim_integration_acceptance_test.go @@ -2,9 +2,11 @@ package resources_test import ( "fmt" - "strings" "testing" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/planchecks" + tfjson "github.com/hashicorp/terraform-json" + acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/helpers" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/snowflakeroles" @@ -20,8 +22,10 @@ import ( func TestAcc_ScimIntegration_basic(t *testing.T) { networkPolicy, networkPolicyCleanup := acc.TestClient().NetworkPolicy.CreateNetworkPolicy(t) t.Cleanup(networkPolicyCleanup) + role, role2 := snowflakeroles.GenericScimProvisioner, snowflakeroles.OktaProvisioner id := acc.TestClient().Ids.RandomAccountObjectIdentifier() + m := func(enabled bool, scimClient sdk.ScimSecurityIntegrationScimClientOption, runAsRole sdk.AccountObjectIdentifier, complete bool) map[string]config.Variable { c := map[string]config.Variable{ "name": config.StringVariable(id.Name()), @@ -36,6 +40,7 @@ func TestAcc_ScimIntegration_basic(t *testing.T) { } return c } + resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, PreCheck: func() { acc.TestAccPreCheck(t) }, @@ -53,7 +58,6 @@ func TestAcc_ScimIntegration_basic(t *testing.T) { resource.TestCheckResourceAttr("snowflake_scim_integration.test", "scim_client", "GENERIC"), resource.TestCheckResourceAttr("snowflake_scim_integration.test", "run_as_role", role.Name()), resource.TestCheckResourceAttrSet("snowflake_scim_integration.test", "sync_password"), - resource.TestCheckResourceAttrSet("snowflake_scim_integration.test", "created_on"), ), }, { @@ -64,10 +68,9 @@ func TestAcc_ScimIntegration_basic(t *testing.T) { resource.TestCheckResourceAttr("snowflake_scim_integration.test", "enabled", "true"), resource.TestCheckResourceAttr("snowflake_scim_integration.test", "scim_client", "OKTA"), resource.TestCheckResourceAttr("snowflake_scim_integration.test", "run_as_role", role2.Name()), - resource.TestCheckResourceAttr("snowflake_scim_integration.test", "network_policy", sdk.NewAccountObjectIdentifier(networkPolicy.Name).FullyQualifiedName()), + resource.TestCheckResourceAttr("snowflake_scim_integration.test", "network_policy", sdk.NewAccountObjectIdentifier(networkPolicy.Name).Name()), // TODO(SNOW-999049): Fix during identifiers rework resource.TestCheckResourceAttr("snowflake_scim_integration.test", "sync_password", "false"), resource.TestCheckResourceAttr("snowflake_scim_integration.test", "comment", "foo"), - resource.TestCheckResourceAttrSet("snowflake_scim_integration.test", "created_on"), ), }, { @@ -87,9 +90,8 @@ func TestAcc_ScimIntegration_basic(t *testing.T) { resource.TestCheckResourceAttr("snowflake_scim_integration.test", "scim_client", "OKTA"), resource.TestCheckResourceAttr("snowflake_scim_integration.test", "run_as_role", role2.Name()), resource.TestCheckResourceAttr("snowflake_scim_integration.test", "network_policy", ""), - resource.TestCheckResourceAttr("snowflake_scim_integration.test", "sync_password", "true"), + resource.TestCheckResourceAttr("snowflake_scim_integration.test", "sync_password", "unknown"), resource.TestCheckResourceAttr("snowflake_scim_integration.test", "comment", ""), - resource.TestCheckResourceAttrSet("snowflake_scim_integration.test", "created_on"), ), }, }, @@ -105,7 +107,7 @@ func TestAcc_ScimIntegration_complete(t *testing.T) { return map[string]config.Variable{ "name": config.StringVariable(id.Name()), "enabled": config.BoolVariable(false), - "scim_client": config.StringVariable(strings.ToLower(string(sdk.ScimSecurityIntegrationScimClientGeneric))), + "scim_client": config.StringVariable(string(sdk.ScimSecurityIntegrationScimClientGeneric)), "sync_password": config.BoolVariable(false), "network_policy_name": config.StringVariable(networkPolicy.Name), "run_as_role": config.StringVariable(role.Name()), @@ -128,10 +130,9 @@ func TestAcc_ScimIntegration_complete(t *testing.T) { resource.TestCheckResourceAttr("snowflake_scim_integration.test", "enabled", "false"), resource.TestCheckResourceAttr("snowflake_scim_integration.test", "scim_client", "GENERIC"), resource.TestCheckResourceAttr("snowflake_scim_integration.test", "run_as_role", role.Name()), - resource.TestCheckResourceAttr("snowflake_scim_integration.test", "network_policy", sdk.NewAccountObjectIdentifier(networkPolicy.Name).FullyQualifiedName()), + resource.TestCheckResourceAttr("snowflake_scim_integration.test", "network_policy", sdk.NewAccountObjectIdentifier(networkPolicy.Name).Name()), // TODO(SNOW-999049): Fix during identifiers rework resource.TestCheckResourceAttr("snowflake_scim_integration.test", "sync_password", "false"), resource.TestCheckResourceAttr("snowflake_scim_integration.test", "comment", "foo"), - resource.TestCheckResourceAttrSet("snowflake_scim_integration.test", "created_on"), ), }, { @@ -230,8 +231,18 @@ func TestAcc_ScimIntegration_migrateFromVersion091(t *testing.T) { { ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, Config: scimIntegrationv092(id.Name(), role.Name()), + // TODO: Remove (?) + ExpectNonEmptyPlan: true, ConfigPlanChecks: resource.ConfigPlanChecks{ - PreApply: []plancheck.PlanCheck{plancheck.ExpectEmptyPlan()}, + PreApply: []plancheck.PlanCheck{ + planchecks.ExpectChange("snowflake_scim_integration.test", "name", tfjson.ActionUpdate, sdk.String(id.Name()), sdk.String(id.Name())), + planchecks.ExpectChange("snowflake_scim_integration.test", "enabled", tfjson.ActionUpdate, nil, nil), + planchecks.ExpectChange("snowflake_scim_integration.test", "scim_client", tfjson.ActionUpdate, sdk.String("GENERIC"), sdk.String("GENERIC")), + planchecks.ExpectChange("snowflake_scim_integration.test", "run_as_role", tfjson.ActionUpdate, sdk.String(role.Name()), sdk.String(role.Name())), + planchecks.ExpectChange("snowflake_scim_integration.test", "network_policy", tfjson.ActionUpdate, sdk.String(""), sdk.String("")), + planchecks.ExpectChange("snowflake_scim_integration.test", "sync_password", tfjson.ActionUpdate, nil, sdk.String("unknown")), + planchecks.ExpectChange("snowflake_scim_integration.test", "comment", tfjson.ActionUpdate, nil, nil), + }, }, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "name", id.Name()), diff --git a/pkg/resources/scim_integration_state_upgraders.go b/pkg/resources/scim_integration_state_upgraders.go index 130ae41076..bafd4534e9 100644 --- a/pkg/resources/scim_integration_state_upgraders.go +++ b/pkg/resources/scim_integration_state_upgraders.go @@ -2,7 +2,7 @@ package resources import "context" -func v091ScimIntegrationStateUpgrader(ctx context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) { +func v091ScimIntegrationStateUpgrader(ctx context.Context, rawState map[string]any, meta any) (map[string]any, error) { if rawState == nil { return rawState, nil } diff --git a/pkg/resources/warehouse.go b/pkg/resources/warehouse.go index bdc51b0881..46b94da60b 100644 --- a/pkg/resources/warehouse.go +++ b/pkg/resources/warehouse.go @@ -29,42 +29,42 @@ var warehouseSchema = map[string]*schema.Schema{ Type: schema.TypeString, Optional: true, ValidateDiagFunc: sdkValidation(sdk.ToWarehouseType), - DiffSuppressFunc: SuppressIfAny(NormalizeAndCompare(sdk.ToWarehouseType), IgnoreChangeToCurrentSnowflakeValue("type")), + DiffSuppressFunc: SuppressIfAny(NormalizeAndCompare(sdk.ToWarehouseType), IgnoreChangeToCurrentSnowflakeValueInShow("type")), Description: fmt.Sprintf("Specifies warehouse type. Valid values are (case-insensitive): %s. Warehouse needs to be suspended to change its type. Provider will handle automatic suspension and resumption if needed.", possibleValuesListed(sdk.ValidWarehouseTypesString)), }, "warehouse_size": { Type: schema.TypeString, Optional: true, ValidateDiagFunc: sdkValidation(sdk.ToWarehouseSize), - DiffSuppressFunc: SuppressIfAny(NormalizeAndCompare(sdk.ToWarehouseSize), IgnoreChangeToCurrentSnowflakeValue("size")), + DiffSuppressFunc: SuppressIfAny(NormalizeAndCompare(sdk.ToWarehouseSize), IgnoreChangeToCurrentSnowflakeValueInShow("size")), Description: fmt.Sprintf("Specifies the size of the virtual warehouse. Valid values are (case-insensitive): %s. Consult [warehouse documentation](https://docs.snowflake.com/en/sql-reference/sql/create-warehouse#optional-properties-objectproperties) for the details. Note: removing the size from config will result in the resource recreation.", possibleValuesListed(sdk.ValidWarehouseSizesString)), }, "max_cluster_count": { Type: schema.TypeInt, Optional: true, ValidateFunc: validation.IntBetween(1, 10), - DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValue("max_cluster_count"), + DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValueInShow("max_cluster_count"), Description: "Specifies the maximum number of server clusters for the warehouse.", }, "min_cluster_count": { Type: schema.TypeInt, Optional: true, ValidateFunc: validation.IntBetween(1, 10), - DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValue("min_cluster_count"), + DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValueInShow("min_cluster_count"), Description: "Specifies the minimum number of server clusters for the warehouse (only applies to multi-cluster warehouses).", }, "scaling_policy": { Type: schema.TypeString, Optional: true, ValidateDiagFunc: sdkValidation(sdk.ToScalingPolicy), - DiffSuppressFunc: SuppressIfAny(NormalizeAndCompare(sdk.ToWarehouseType), IgnoreChangeToCurrentSnowflakeValue("scaling_policy")), + DiffSuppressFunc: SuppressIfAny(NormalizeAndCompare(sdk.ToWarehouseType), IgnoreChangeToCurrentSnowflakeValueInShow("scaling_policy")), Description: fmt.Sprintf("Specifies the policy for automatically starting and shutting down clusters in a multi-cluster warehouse running in Auto-scale mode. Valid values are (case-insensitive): %s.", possibleValuesListed(sdk.ValidWarehouseScalingPoliciesString)), }, "auto_suspend": { Type: schema.TypeInt, Optional: true, ValidateFunc: validation.IntAtLeast(0), - DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValue("auto_suspend"), + DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValueInShow("auto_suspend"), Description: "Specifies the number of seconds of inactivity after which a warehouse is automatically suspended.", Default: -1, }, @@ -72,7 +72,7 @@ var warehouseSchema = map[string]*schema.Schema{ Type: schema.TypeString, Optional: true, ValidateFunc: validation.StringInSlice([]string{"true", "false"}, true), - DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValue("auto_resume"), + DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValueInShow("auto_resume"), Description: "Specifies whether to automatically resume a warehouse when a SQL statement (e.g. query) is submitted to it.", Default: "unknown", }, @@ -86,7 +86,7 @@ var warehouseSchema = map[string]*schema.Schema{ Type: schema.TypeString, Optional: true, ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), - DiffSuppressFunc: SuppressIfAny(suppressIdentifierQuoting, IgnoreChangeToCurrentSnowflakeValue("resource_monitor")), + DiffSuppressFunc: SuppressIfAny(suppressIdentifierQuoting, IgnoreChangeToCurrentSnowflakeValueInShow("resource_monitor")), Description: "Specifies the name of a resource monitor that is explicitly assigned to the warehouse.", }, "comment": { @@ -98,7 +98,7 @@ var warehouseSchema = map[string]*schema.Schema{ Type: schema.TypeString, Optional: true, ValidateFunc: validation.StringInSlice([]string{"true", "false"}, true), - DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValue("enable_query_acceleration"), + DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValueInShow("enable_query_acceleration"), Description: "Specifies whether to enable the query acceleration service for queries that rely on this warehouse for compute resources.", Default: "unknown", }, @@ -106,7 +106,7 @@ var warehouseSchema = map[string]*schema.Schema{ Type: schema.TypeInt, Optional: true, ValidateFunc: validation.IntBetween(0, 100), - DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValue("query_acceleration_max_scale_factor"), + DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValueInShow("query_acceleration_max_scale_factor"), Description: "Specifies the maximum scale factor for leasing compute resources for query acceleration. The scale factor is used as a multiplier based on warehouse size.", Default: -1, }, @@ -337,7 +337,7 @@ func GetReadWarehouseFunc(withExternalChangesMarking bool) schema.ReadContextFun } if withExternalChangesMarking { - if err = handleExternalChangesToObject(d, + if err = handleExternalChangesToObjectInShow(d, showMapping{"type", "warehouse_type", string(w.Type), w.Type, nil}, showMapping{"size", "warehouse_size", string(w.Size), w.Size, nil}, showMapping{"max_cluster_count", "max_cluster_count", w.MaxClusterCount, w.MaxClusterCount, nil}, diff --git a/pkg/resources/warehouse_rework_show_output_proposal.go b/pkg/resources/warehouse_rework_show_output_proposal.go index 8c9908953c..74fc54b1f4 100644 --- a/pkg/resources/warehouse_rework_show_output_proposal.go +++ b/pkg/resources/warehouse_rework_show_output_proposal.go @@ -4,10 +4,13 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -const showOutputAttributeName = "show_output" +const ( + showOutputAttributeName = "show_output" + describeOutputAttributeName = "describe_output" +) -// handleExternalChangesToObject assumes that show output is kept in showOutputAttributeName attribute -func handleExternalChangesToObject(d *schema.ResourceData, mappings ...showMapping) error { +// handleExternalChangesToObjectInShow assumes that show output is kept in showOutputAttributeName attribute +func handleExternalChangesToObjectInShow(d *schema.ResourceData, mappings ...showMapping) error { if showOutput, ok := d.GetOk(showOutputAttributeName); ok { showOutputList := showOutput.([]any) if len(showOutputList) == 1 { @@ -35,3 +38,43 @@ type showMapping struct { valueToSet any normalizeFunc func(any) any } + +// handleExternalChangesToObjectInDescribe assumes that show output is kept in describeOutputAttributeName attribute +func handleExternalChangesToObjectInDescribe(d *schema.ResourceData, mappings ...describeMapping) error { + if describeOutput, ok := d.GetOk(showOutputAttributeName); ok { + describeOutputList := describeOutput.([]any) + if len(describeOutputList) == 1 { + result := describeOutputList[0].(map[string]any) + + for _, mapping := range mappings { + if result[mapping.nameInDescribe] == nil { + continue + } + + valueToCompareFromList := result[mapping.nameInDescribe].([]map[string]any) + if len(valueToCompareFromList) != 1 { + continue + } + + valueToCompareFrom := valueToCompareFromList[0]["value"] + if mapping.normalizeFunc != nil { + valueToCompareFrom = mapping.normalizeFunc(valueToCompareFrom) + } + if valueToCompareFrom != mapping.valueToCompare { + if err := d.Set(mapping.nameInConfig, mapping.valueToSet); err != nil { + return err + } + } + } + } + } + return nil +} + +type describeMapping struct { + nameInDescribe string + nameInConfig string + valueToCompare any + valueToSet any + normalizeFunc func(any) any +} diff --git a/pkg/schemas/common_types.go b/pkg/schemas/common_types.go new file mode 100644 index 0000000000..36db0c3692 --- /dev/null +++ b/pkg/schemas/common_types.go @@ -0,0 +1,42 @@ +package schemas + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +// ParameterListSchema represents Snowflake parameter object. +var ParameterListSchema = &schema.Schema{ + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: ShowParameterSchema, + }, +} + +// DescribePropertyListSchema represents Snowflake property object returned by DESCRIBE query. +var DescribePropertyListSchema = &schema.Schema{ + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: DescribePropertySchema, + }, +} + +var DescribePropertySchema = map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + }, + "type": { + Type: schema.TypeString, + Computed: true, + }, + "value": { + Type: schema.TypeString, + Computed: true, + }, + "default": { + Type: schema.TypeString, + Computed: true, + }, +} diff --git a/pkg/schemas/parameters.go b/pkg/schemas/parameters.go deleted file mode 100644 index b81d3b0786..0000000000 --- a/pkg/schemas/parameters.go +++ /dev/null @@ -1,12 +0,0 @@ -package schemas - -import "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - -// ParameterListSchema represents Snowflake parameter object. -var ParameterListSchema = &schema.Schema{ - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: ShowParameterSchema, - }, -} diff --git a/pkg/schemas/security_integration.go b/pkg/schemas/security_integration.go new file mode 100644 index 0000000000..8cbee8f863 --- /dev/null +++ b/pkg/schemas/security_integration.go @@ -0,0 +1,60 @@ +package schemas + +import ( + "strings" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +// DescribeSecurityIntegrationSchema represents output of DESCRIBE query for the single SecurityIntegration. +var DescribeSecurityIntegrationSchema = map[string]*schema.Schema{ + "enabled": DescribePropertyListSchema, + "network_policy": DescribePropertyListSchema, + "run_as_role": DescribePropertyListSchema, + "sync_password": DescribePropertyListSchema, + "comment": DescribePropertyListSchema, +} + +var _ = DescribeSecurityIntegrationSchema + +var ( + // TODO(1501905): Should be generated by SHOW generator + ShowSecurityIntegrationSchemaKeys = make([]string, 0, len(ShowSecurityIntegrationSchema)) + DescribeSecurityIntegrationSchemaKeys = make([]string, 0, len(DescribeSecurityIntegrationSchema)) +) + +func init() { + // TODO(1501905): Should be generated by SHOW generator + for key := range ShowSecurityIntegrationSchema { + ShowSecurityIntegrationSchemaKeys = append(ShowSecurityIntegrationSchemaKeys, key) + } + + for key := range DescribeSecurityIntegrationSchema { + DescribeSecurityIntegrationSchemaKeys = append(DescribeSecurityIntegrationSchemaKeys, key) + } +} + +func SecurityIntegrationPropertiesToSchema(securityIntegrationProperties []sdk.SecurityIntegrationProperty) map[string]any { + securityIntegrationSchema := make(map[string]any) + for _, securityIntegrationProperty := range securityIntegrationProperties { + switch securityIntegrationProperty.Name { + case "ENABLED", + "NETWORK_POLICY", + "RUN_AS_ROLE", + "SYNC_PASSWORD", + "COMMENT": + securityIntegrationSchema[strings.ToLower(securityIntegrationProperty.Name)] = []map[string]any{ + { + "name": securityIntegrationProperty.Name, + "type": securityIntegrationProperty.Type, + "value": securityIntegrationProperty.Value, + "default": securityIntegrationProperty.Default, + }, + } + } + } + return securityIntegrationSchema +} + +var _ = SecurityIntegrationPropertiesToSchema From e65ffad5147f485f07d303aaaa60c3020cf35a79 Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Fri, 28 Jun 2024 08:49:12 +0200 Subject: [PATCH 02/14] Adjust external oauth --- .../import.sh | 2 +- .../resource.tf | 54 +- pkg/resources/common.go | 28 + pkg/resources/custom_diffs.go | 45 + pkg/resources/external_oauth_integration.go | 850 +++++++++--------- ...ernal_oauth_integration_acceptance_test.go | 221 ++--- pkg/resources/helper_expansion.go | 14 + .../basic/test.tf | 19 + .../basic/variables.tf | 52 ++ .../completeWithJwsKeysUrl/test.tf | 15 + .../completeWithJwsKeysUrl/variables.tf | 40 + .../invalid/test.tf | 19 + .../invalid/variables.tf | 52 ++ pkg/sdk/security_integrations_def.go | 60 ++ 14 files changed, 894 insertions(+), 577 deletions(-) create mode 100644 pkg/resources/testdata/TestAcc_ExternalOauthIntegration/basic/test.tf create mode 100644 pkg/resources/testdata/TestAcc_ExternalOauthIntegration/basic/variables.tf create mode 100644 pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrl/test.tf create mode 100644 pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrl/variables.tf create mode 100644 pkg/resources/testdata/TestAcc_ExternalOauthIntegration/invalid/test.tf create mode 100644 pkg/resources/testdata/TestAcc_ExternalOauthIntegration/invalid/variables.tf diff --git a/examples/resources/snowflake_external_oauth_integration/import.sh b/examples/resources/snowflake_external_oauth_integration/import.sh index feb48d066e..8029ac973e 100644 --- a/examples/resources/snowflake_external_oauth_integration/import.sh +++ b/examples/resources/snowflake_external_oauth_integration/import.sh @@ -1 +1 @@ -terraform import snowflake_external_oauth_integration.example name +terraform import snowflake_external_oauth_integration.example "name" diff --git a/examples/resources/snowflake_external_oauth_integration/resource.tf b/examples/resources/snowflake_external_oauth_integration/resource.tf index 269e02f8f1..bf3f9c0f88 100644 --- a/examples/resources/snowflake_external_oauth_integration/resource.tf +++ b/examples/resources/snowflake_external_oauth_integration/resource.tf @@ -1,10 +1,44 @@ -resource "snowflake_external_oauth_integration" "azure" { - name = "AZURE_POWERBI" - type = "AZURE" - enabled = true - issuer = "https://sts.windows.net/00000000-0000-0000-0000-000000000000" - snowflake_user_mapping_attribute = "LOGIN_NAME" - jws_keys_urls = ["https://login.windows.net/common/discovery/keys"] - audience_urls = ["https://analysis.windows.net/powerbi/connector/Snowflake"] - token_user_mapping_claims = ["upn"] -} \ No newline at end of file +# basic resource +resource "snowflake_external_oauth_integration" "test" { + enabled = true + external_oauth_issuer = "foo" + external_oauth_snowflake_user_mapping_attribute = "LOGIN_NAME" + external_oauth_token_user_mapping_claims = ["foo"] + name = "foo" + external_oauth_type = "CUSTOM" +} +# resource with all fields set (jws keys url flow) +resource "snowflake_external_oauth_integration" "test" { + comment = "foo" + enabled = true + external_oauth_allowed_roles_list = ["foo"] + external_oauth_any_role_mode = "ENABLED" + external_oauth_audience_list = ["foo"] + external_oauth_blocked_roles_list = ["bar"] + external_oauth_issuer = "foo" + external_oauth_jws_keys_url = ["https://example.com"] + external_oauth_scope_delimiter = "," + external_oauth_scope_mapping_attribute = "LOGIN_NAME" + external_oauth_snowflake_user_mapping_attribute = "foo" + external_oauth_token_user_mapping_claims = ["foo"] + name = "foo" + external_oauth_type = "CUSTOM" +} +# resource with all fields set (rsa public key flow) +resource "snowflake_external_oauth_integration" "test" { + comment = "foo" + enabled = true + external_oauth_allowed_roles_list = ["foo"] + external_oauth_any_role_mode = "ENABLED" + external_oauth_audience_list = ["foo"] + external_oauth_blocked_roles_list = ["bar"] + external_oauth_issuer = "foo" + external_oauth_rsa_public_key = file("key.pem") + external_oauth_rsa_public_key_2 = file("key2.pem") + external_oauth_scope_delimiter = "foo" + external_oauth_scope_mapping_attribute = "LOGIN_NAME" + external_oauth_snowflake_user_mapping_attribute = "foo" + external_oauth_token_user_mapping_claims = ["foo"] + name = "foo" + external_oauth_type = "CUSTOM" +} diff --git a/pkg/resources/common.go b/pkg/resources/common.go index 5cfbf007a4..8347591a77 100644 --- a/pkg/resources/common.go +++ b/pkg/resources/common.go @@ -1,6 +1,12 @@ package resources import ( + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "errors" + "fmt" "strings" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" @@ -52,3 +58,25 @@ func suppressCopyOptionsQuoting(_, oldValue, newValue string, _ *schema.Resource return oldWithoutQuotes == newWithoutQuotes } } + +func RSAKeyHash(key string) (string, error) { + keyBytes := []byte(fmt.Sprintf("-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----", key)) + + block, _ := pem.Decode(keyBytes) + if block == nil || block.Type != "PUBLIC KEY" { + return "", errors.New("Failed to decode PEM block containing public key") + } + + pubKey, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return "", fmt.Errorf("Unable to parse public key: %w", err) + } + + pubKeyBytes, err := x509.MarshalPKIXPublicKey(pubKey) + if err != nil { + return "", fmt.Errorf("Unable to marshal public key: %w", err) + } + + hash := sha256.Sum256(pubKeyBytes) + return fmt.Sprintf("SHA256:%s", base64.StdEncoding.EncodeToString(hash[:])), nil +} diff --git a/pkg/resources/custom_diffs.go b/pkg/resources/custom_diffs.go index 14382da0af..76e085f06d 100644 --- a/pkg/resources/custom_diffs.go +++ b/pkg/resources/custom_diffs.go @@ -2,10 +2,13 @@ package resources import ( "context" + "fmt" "log" "strconv" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" @@ -72,3 +75,45 @@ func ComputedIfAnyAttributeChanged(key string, changedAttributeKeys ...string) s return result }) } + +// ForceNewIfChangeToEmptyString sets a ForceNew for a string field which was set to an empty value. +func ForceNewIfChangeToEmptyString(key string) schema.CustomizeDiffFunc { + return customdiff.ForceNewIfChange(key, func(ctx context.Context, oldValue, newValue, meta any) bool { + oldString, newString := oldValue.(string), newValue.(string) + return len(oldString) > 0 && len(newString) == 0 + }) +} + +// ForceNewIfChangeToEmptySet sets a ForceNew for a set field which was set to an empty value. +func ForceNewIfChangeToEmptySet[T any](key string) schema.CustomizeDiffFunc { + return customdiff.ForceNewIfChange(key, func(ctx context.Context, oldValue, newValue, meta any) bool { + oldList, newList := oldValue.(*schema.Set).List(), newValue.(*schema.Set).List() + return len(oldList) > 0 && len(newList) == 0 + }) +} + +func ModifyStateIfParameterSet(key, param string, modify func(*schema.ResourceDiff) error) schema.CustomizeDiffFunc { + return func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error { + client := meta.(*provider.Context).Client + params, err := client.Parameters.ShowParameters(ctx, &sdk.ShowParametersOptions{ + Like: &sdk.Like{ + Pattern: sdk.Pointer(param), + }, + In: &sdk.ParametersIn{ + Account: sdk.Pointer(true), + }, + }) + if err != nil { + return err + } + found, err := collections.FindOne(params, func(p *sdk.Parameter) bool { return p.Key == param }) + if err != nil { + return fmt.Errorf("parameter %s not found", param) + } + param := helpers.StringToBool((*found).Value) + if !param { + return nil + } + return modify(d) + } +} diff --git a/pkg/resources/external_oauth_integration.go b/pkg/resources/external_oauth_integration.go index 0cea6460c0..6d0ef97dca 100644 --- a/pkg/resources/external_oauth_integration.go +++ b/pkg/resources/external_oauth_integration.go @@ -1,13 +1,19 @@ package resources import ( - "database/sql" + "context" + "errors" "fmt" + "log" + "reflect" "strings" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/snowflake" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) @@ -19,16 +25,11 @@ var oauthExternalIntegrationSchema = map[string]*schema.Schema{ ForceNew: true, Description: "Specifies the name of the External Oath integration. This name follows the rules for Object Identifiers. The name should be unique among security integrations in your account.", }, - "type": { - Type: schema.TypeString, - Required: true, - Description: "Specifies the OAuth 2.0 authorization server to be Okta, Microsoft Azure AD, Ping Identity PingFederate, or a Custom OAuth 2.0 authorization server.", - ValidateFunc: validation.StringInSlice([]string{ - string(snowflake.Okta), - string(snowflake.Azure), - string(snowflake.PingFederate), - string(snowflake.Custom), - }, true), + "external_oauth_type": { + Type: schema.TypeString, + Required: true, + Description: fmt.Sprintf("Specifies the OAuth 2.0 authorization server to be Okta, Microsoft Azure AD, Ping Identity PingFederate, or a Custom OAuth 2.0 authorization server. Valid options are: %v", sdk.AllExternalOauthSecurityIntegrationTypes), + ValidateFunc: validation.StringInSlice(sdk.AsStringList(sdk.AllExternalOauthSecurityIntegrationTypes), true), DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { normalize := func(s string) string { return strings.ToUpper(strings.ReplaceAll(s, "-", "")) @@ -41,30 +42,22 @@ var oauthExternalIntegrationSchema = map[string]*schema.Schema{ Required: true, Description: "Specifies whether to initiate operation of the integration or suspend it.", }, - "issuer": { + "external_oauth_issuer": { Type: schema.TypeString, Required: true, Description: "Specifies the URL to define the OAuth 2.0 authorization server.", }, - "token_user_mapping_claims": { + "external_oauth_token_user_mapping_claims": { Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, Required: true, Description: "Specifies the access token claim or claims that can be used to map the access token to a Snowflake user record.", }, - "scope_mapping_attribute": { - Type: schema.TypeString, - Optional: true, - Description: "Specifies the access token claim to map the access token to an account role.", - }, - "snowflake_user_mapping_attribute": { - Type: schema.TypeString, - Required: true, - Description: "Indicates which Snowflake user record attribute should be used to map the access token to a Snowflake user record.", - ValidateFunc: validation.StringInSlice([]string{ - string(snowflake.LoginName), - string(snowflake.EmailAddress), - }, true), + "external_oauth_snowflake_user_mapping_attribute": { + Type: schema.TypeString, + Required: true, + Description: fmt.Sprintf("Indicates which Snowflake user record attribute should be used to map the access token to a Snowflake user record. Valid options are: %v", sdk.AllExternalOauthSecurityIntegrationSnowflakeUserMappingAttributes), + ValidateFunc: validation.StringInSlice(sdk.AsStringList(sdk.AllExternalOauthSecurityIntegrationSnowflakeUserMappingAttributes), true), DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { normalize := func(s string) string { return strings.ToUpper(strings.ReplaceAll(s, "-", "")) @@ -72,51 +65,60 @@ var oauthExternalIntegrationSchema = map[string]*schema.Schema{ return normalize(old) == normalize(new) }, }, - "jws_keys_urls": { - Type: schema.TypeSet, - Elem: &schema.Schema{Type: schema.TypeString}, - MaxItems: 3, - Optional: true, - Description: "Specifies the endpoint or a list of endpoints from which to download public keys or certificates to validate an External OAuth access token. The maximum number of URLs that can be specified in the list is 3.", + "external_oauth_jws_keys_url": { + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + MaxItems: 3, + Optional: true, + ConflictsWith: []string{"external_oauth_rsa_public_key"}, + Description: "Specifies the endpoint or a list of endpoints from which to download public keys or certificates to validate an External OAuth access token. The maximum number of URLs that can be specified in the list is 3.", }, - "rsa_public_key": { - Type: schema.TypeString, - Optional: true, - Description: "Specifies a Base64-encoded RSA public key, without the -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY----- headers.", + "external_oauth_rsa_public_key": { + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"external_oauth_jws_keys_url"}, + Description: "Specifies a Base64-encoded RSA public key, without the -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY----- headers.", }, - "rsa_public_key_2": { - Type: schema.TypeString, - Optional: true, - Description: "Specifies a second RSA public key, without the -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY----- headers. Used for key rotation.", + "external_oauth_rsa_public_key_2": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies a second RSA public key, without the -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY----- headers. Used for key rotation.", + ConflictsWith: []string{"external_oauth_jws_keys_url"}, }, - "blocked_roles": { + "external_oauth_blocked_roles_list": { Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, Optional: true, - Description: "Specifies the list of roles that a client cannot set as the primary role. Do not include ACCOUNTADMIN, ORGADMIN or SECURITYADMIN as they are already implicitly enforced and will cause in-place updates.", + Computed: true, + Description: "Specifies the list of roles that a client cannot set as the primary role. By default, this list includes the ACCOUNTADMIN, ORGADMIN, and SECURITYADMIN roles. To remove these privileged roles from the list, use the ALTER ACCOUNT command to set the EXTERNAL_OAUTH_ADD_PRIVILEGED_ROLES_TO_BLOCKED_LIST account parameter to FALSE.", + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + value := d.Get("external_oauth_add_privileged_roles_to_blocked_list").(bool) + if !value { + return old == new + } + return old == "ACCOUNTADMIN" || old == "SECURITYADMIN" + }, + ConflictsWith: []string{"external_oauth_allowed_roles_list"}, }, - "allowed_roles": { - Type: schema.TypeSet, - Elem: &schema.Schema{Type: schema.TypeString}, - Optional: true, - Description: "Specifies the list of roles that the client can set as the primary role.", + "external_oauth_allowed_roles_list": { + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + Description: "Specifies the list of roles that the client can set as the primary role.", + ConflictsWith: []string{"external_oauth_blocked_roles_list"}, }, - "audience_urls": { + "external_oauth_audience_list": { Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, Optional: true, Description: "Specifies additional values that can be used for the access token's audience validation on top of using the Customer's Snowflake Account URL ", }, - "any_role_mode": { - Type: schema.TypeString, - Optional: true, - Default: string(snowflake.Disable), - Description: "Specifies whether the OAuth client or user can use a role that is not defined in the OAuth access token.", - ValidateFunc: validation.StringInSlice([]string{ - string(snowflake.Disable), - string(snowflake.Enable), - string(snowflake.EnableForPrivilege), - }, true), + "external_oauth_any_role_mode": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "Specifies whether the OAuth client or user can use a role that is not defined in the OAuth access token.", + ValidateFunc: validation.StringInSlice(sdk.AsStringList(sdk.AllExternalOauthSecurityIntegrationAnyRoleModes), true), DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { normalize := func(s string) string { return strings.ToUpper(strings.ReplaceAll(s, "-", "")) @@ -124,11 +126,17 @@ var oauthExternalIntegrationSchema = map[string]*schema.Schema{ return normalize(old) == normalize(new) }, }, - "scope_delimiter": { + "external_oauth_scope_delimiter": { Type: schema.TypeString, Optional: true, + Computed: true, Description: "Specifies the scope delimiter in the authorization token.", }, + "external_oauth_scope_mapping_attribute": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the access token claim to map the access token to an account role.", + }, "comment": { Type: schema.TypeString, Optional: true, @@ -139,450 +147,448 @@ var oauthExternalIntegrationSchema = map[string]*schema.Schema{ Computed: true, Description: "Date and time when the External OAUTH integration was created.", }, + "external_oauth_add_privileged_roles_to_blocked_list": { + Type: schema.TypeBool, + Computed: true, + }, } -// ExternalOauthIntegration returns a pointer to the resource representing a network policy. func ExternalOauthIntegration() *schema.Resource { return &schema.Resource{ - Description: "An External OAuth security integration allows a client to use a third-party authorization server to obtain the access tokens needed to interact with Snowflake.", - Create: CreateExternalOauthIntegration, - Read: ReadExternalOauthIntegration, - Update: UpdateExternalOauthIntegration, - Delete: DeleteExternalOauthIntegration, - - Schema: oauthExternalIntegrationSchema, + CreateContext: CreateContextExternalOauthIntegration, + ReadContext: ReadContextExternalOauthIntegration(true), + UpdateContext: UpdateContextExternalOauthIntegration, + DeleteContext: DeleteContextExternalOauthIntegration, + Schema: oauthExternalIntegrationSchema, + CustomizeDiff: customdiff.All( + ForceNewIfChangeToEmptyString("external_oauth_snowflake_user_mapping_attribute"), + ForceNewIfChangeToEmptyString("external_oauth_rsa_public_key"), + ForceNewIfChangeToEmptyString("external_oauth_rsa_public_key_2"), + ForceNewIfChangeToEmptyString("external_oauth_scope_mapping_attribute"), + ForceNewIfChangeToEmptySet[any]("external_oauth_jws_keys_url"), + ModifyStateIfParameterSet("external_oauth_blocked_roles_list", "EXTERNAL_OAUTH_ADD_PRIVILEGED_ROLES_TO_BLOCKED_LIST", func(d *schema.ResourceDiff) error { + allowed := d.Get("external_oauth_allowed_roles_list").(*schema.Set) + if allowed.Len() > 0 { + return nil + } + set := d.Get("external_oauth_blocked_roles_list").(*schema.Set) + set.Add("ACCOUNTADMIN") + set.Add("SECURITYADMIN") + return d.SetNew("external_oauth_blocked_roles_list", set) + }), + ), Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, } } -// CreateExternalOauthIntegration implements schema.CreateFunc. -func CreateExternalOauthIntegration(d *schema.ResourceData, meta interface{}) error { - manager, err := snowflake.NewExternalOauthIntegration3Manager() +func CreateContextExternalOauthIntegration(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + enabled := d.Get("enabled").(bool) + externalOauthIssuer := d.Get("external_oauth_issuer").(string) + externalOauthSnowflakeUserMappingAttributeRaw := d.Get("external_oauth_snowflake_user_mapping_attribute").(string) + externalOauthTokenUserMappingClaimRaw := expandStringList(d.Get("external_oauth_token_user_mapping_claims").(*schema.Set).List()) + name := d.Get("name").(string) + integrationTypeRaw := d.Get("external_oauth_type").(string) + integrationType, err := sdk.ToExternalOauthSecurityIntegrationTypeOption(integrationTypeRaw) if err != nil { - return fmt.Errorf("couldn't create external oauth integration manager: %w", err) - } - - input := &snowflake.ExternalOauthIntegration3CreateInput{ - ExternalOauthIntegration3: snowflake.ExternalOauthIntegration3{ - TopLevelIdentifier: snowflake.TopLevelIdentifier{ - Name: d.Get("name").(string), - }, - - Type: "EXTERNAL_OAUTH", - TypeOk: true, - Enabled: d.Get("enabled").(bool), - EnabledOk: isOk(d.GetOk("enabled")), - ExternalOauthType: snowflake.ExternalOauthType(d.Get("type").(string)), - ExternalOauthTypeOk: isOk(d.GetOk("type")), - ExternalOauthIssuer: d.Get("issuer").(string), - ExternalOauthIssuerOk: isOk(d.GetOk("issuer")), - ExternalOauthTokenUserMappingClaim: expandStringList(d.Get("token_user_mapping_claims").(*schema.Set).List()), - ExternalOauthTokenUserMappingClaimOk: isOk(d.GetOk("token_user_mapping_claims")), - ExternalOauthSnowflakeUserMappingAttribute: snowflake.SFUserMappingAttribute(d.Get("snowflake_user_mapping_attribute").(string)), - ExternalOauthSnowflakeUserMappingAttributeOk: isOk(d.GetOk("snowflake_user_mapping_attribute")), - ExternalOauthJwsKeysURL: expandStringList(d.Get("jws_keys_urls").(*schema.Set).List()), - ExternalOauthJwsKeysURLOk: isOk(d.GetOk("jws_keys_urls")), - ExternalOauthBlockedRolesList: expandStringList(d.Get("blocked_roles").(*schema.Set).List()), - ExternalOauthBlockedRolesListOk: isOk(d.GetOk("blocked_roles")), - ExternalOauthAllowedRolesList: expandStringList(d.Get("allowed_roles").(*schema.Set).List()), - ExternalOauthAllowedRolesListOk: isOk(d.GetOk("allowed_roles")), - ExternalOauthRsaPublicKey: d.Get("rsa_public_key").(string), - ExternalOauthRsaPublicKeyOk: isOk(d.GetOk("rsa_public_key")), - ExternalOauthRsaPublicKey2: d.Get("rsa_public_key_2").(string), - ExternalOauthRsaPublicKey2Ok: isOk(d.GetOk("rsa_public_key_2")), - ExternalOauthAudienceList: expandStringList(d.Get("audience_urls").(*schema.Set).List()), - ExternalOauthAudienceListOk: isOk(d.GetOk("audience_urls")), - ExternalOauthAnyRoleMode: snowflake.AnyRoleMode(d.Get("any_role_mode").(string)), - ExternalOauthAnyRoleModeOk: isOk(d.GetOk("any_role_mode")), - ExternalOauthScopeDelimiter: d.Get("scope_delimiter").(string), - ExternalOauthScopeDelimiterOk: isOk(d.GetOk("scope_delimiter")), - ExternalOauthScopeMappingAttribute: d.Get("scope_mapping_attribute").(string), - ExternalOauthScopeMappingAttributeOk: isOk(d.GetOk("scope_mapping_attribute")), - - Comment: sql.NullString{String: d.Get("comment").(string)}, - CommentOk: isOk(d.GetOk("comment")), - }, + return diag.FromErr(err) } - - stmt, err := manager.Create(input) + externalOauthSnowflakeUserMappingAttribute, err := sdk.ToExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeOption(externalOauthSnowflakeUserMappingAttributeRaw) if err != nil { - return fmt.Errorf("couldn't generate create statement: %w", err) + return diag.FromErr(err) } - - client := meta.(*provider.Context).Client - db := client.GetConn().DB - err = snowflake.Exec(db, stmt) - if err != nil { - return fmt.Errorf("error executing create statement: %w", err) + externalOauthTokenUserMappingClaim := make([]sdk.TokenUserMappingClaim, 0, len(externalOauthTokenUserMappingClaimRaw)) + for _, v := range externalOauthTokenUserMappingClaimRaw { + externalOauthTokenUserMappingClaim = append(externalOauthTokenUserMappingClaim, sdk.TokenUserMappingClaim{Claim: v}) } + id := sdk.NewAccountObjectIdentifier(name) + req := sdk.NewCreateExternalOauthSecurityIntegrationRequest(id, enabled, integrationType, externalOauthIssuer, externalOauthTokenUserMappingClaim, externalOauthSnowflakeUserMappingAttribute) - d.SetId(ExternalOauthIntegrationID(&input.ExternalOauthIntegration3)) - - return ReadExternalOauthIntegration(d, meta) -} - -// ReadExternalOauthIntegration implements schema.ReadFunc. -func ReadExternalOauthIntegration(d *schema.ResourceData, meta interface{}) error { - manager, err := snowflake.NewExternalOauthIntegration3Manager() - if err != nil { - return fmt.Errorf("couldn't create external oauth integration builder: %w", err) + if v, ok := d.GetOk("comment"); ok { + req.WithComment(v.(string)) } - input := ExternalOauthIntegrationIdentifier(d.Id()) - - client := meta.(*provider.Context).Client - db := client.GetConn().DB - - // This resource needs a SHOW and a DESCRIBE - - // SHOW - stmt, err := manager.ReadShow(input) - if err != nil { - return fmt.Errorf("couldn't generate show statement: %w", err) + if v, ok := d.GetOk("external_oauth_allowed_roles_list"); ok { + req.WithExternalOauthAllowedRolesList(sdk.AllowedRolesListRequest{AllowedRolesList: expandObjectIdentifierList(v.(*schema.Set).List())}) } - row := snowflake.QueryRow(db, stmt) - if err != nil { - return fmt.Errorf("error querying external oauth integration: %w", err) + if v, ok := d.GetOk("external_oauth_any_role_mode"); ok { + valueRaw := v.(string) + value, err := sdk.ToExternalOauthSecurityIntegrationAnyRoleModeOption(valueRaw) + if err != nil { + return diag.FromErr(err) + } + req.WithExternalOauthAnyRoleMode(value) } - showOutput, err := manager.ParseShow(row) - if err != nil { - return fmt.Errorf("error parsing show result: %w", err) + if v, ok := d.GetOk("external_oauth_audience_list"); ok { + elems := expandStringList(v.(*schema.Set).List()) + audienceUrls := make([]sdk.AudienceListItem, len(elems)) + for i := range elems { + audienceUrls[i] = sdk.AudienceListItem{Item: elems[i]} + } + req.WithExternalOauthAudienceList(sdk.AudienceListRequest{AudienceList: audienceUrls}) } - if err := d.Set("type", strings.TrimPrefix(showOutput.Type, "EXTERNAL_OAUTH - ")); err != nil { - return fmt.Errorf("error setting type: %w", err) - } - if err := d.Set("name", showOutput.Name); err != nil { - return fmt.Errorf("error setting name: %w", err) - } - if err := d.Set("enabled", showOutput.Enabled); err != nil { - return fmt.Errorf("error setting enabled: %w", err) - } - if err := d.Set("comment", showOutput.Comment.String); err != nil { - return fmt.Errorf("error setting comment: %w", err) + if v, ok := d.GetOk("external_oauth_blocked_roles_list"); ok { + if _, okAllowed := d.GetOk("external_oauth_allowed_roles_list"); !okAllowed { + req.WithExternalOauthBlockedRolesList(sdk.BlockedRolesListRequest{BlockedRolesList: expandObjectIdentifierList(v.(*schema.Set).List())}) + } } - // if err := d.Set("created_on", showOutput.CreatedOn.String); err != nil { - // return fmt.Errorf("error setting created_on: %w", err) - // } - // DESCRIBE - stmt, err = manager.ReadDescribe(input) - if err != nil { - return fmt.Errorf("couldn't generate describe statement: %w", err) + if v, ok := d.GetOk("external_oauth_jws_keys_url"); ok { + elems := expandStringList(v.(*schema.Set).List()) + urls := make([]sdk.JwsKeysUrl, len(elems)) + for i := range elems { + urls[i] = sdk.JwsKeysUrl{JwsKeyUrl: elems[i]} + } + req.WithExternalOauthJwsKeysUrl(urls) } - rows, err := snowflake.Query(db, stmt) - if err != nil { - return fmt.Errorf("error querying external oauth integration: %w", err) + if v, ok := d.GetOk("external_oauth_rsa_public_key"); ok { + req.WithExternalOauthRsaPublicKey(v.(string)) } - defer rows.Close() - describeOutput, err := manager.ParseDescribe(rows.Rows) - if err != nil { - return fmt.Errorf("failed to parse result of describe: %w", err) + if v, ok := d.GetOk("external_oauth_rsa_public_key_2"); ok { + req.WithExternalOauthRsaPublicKey2(v.(string)) } - if err := d.Set("issuer", describeOutput.ExternalOauthIssuer); err != nil { - return fmt.Errorf("error setting issuer: %w", err) - } - if err := d.Set("jws_keys_urls", describeOutput.ExternalOauthJwsKeysURL); err != nil { - return fmt.Errorf("error setting jws_keys_urls: %w", err) - } - if err := d.Set("any_role_mode", describeOutput.ExternalOauthAnyRoleMode); err != nil { - return fmt.Errorf("error setting any_role_mode: %w", err) - } - if err := d.Set("rsa_public_key", describeOutput.ExternalOauthRsaPublicKey); err != nil { - return fmt.Errorf("error setting rsa_public_key: %w", err) + if v, ok := d.GetOk("external_oauth_scope_delimiter"); ok { + req.WithExternalOauthScopeDelimiter(v.(string)) } - if err := d.Set("rsa_public_key_2", describeOutput.ExternalOauthRsaPublicKey2); err != nil { - return fmt.Errorf("error setting rsa_public_key_2: %w", err) - } - // Filter out default roles - blockedRoles := []string{} - for i := range describeOutput.ExternalOauthBlockedRolesList { - role := describeOutput.ExternalOauthBlockedRolesList[i] - if role != "ACCOUNTADMIN" && role != "SECURITYADMIN" { - blockedRoles = append(blockedRoles, role) - } - } - if err := d.Set("blocked_roles", blockedRoles); err != nil { - return fmt.Errorf("error setting blocked_roles: %w", err) - } - if err := d.Set("allowed_roles", describeOutput.ExternalOauthAllowedRolesList); err != nil { - return fmt.Errorf("error setting allowed_roles: %w", err) - } - if err := d.Set("audience_urls", describeOutput.ExternalOauthAudienceList); err != nil { - return fmt.Errorf("error setting audience_urls: %w", err) - } - if err := d.Set("token_user_mapping_claims", describeOutput.ExternalOauthTokenUserMappingClaim); err != nil { - return fmt.Errorf("error setting token_user_mapping_claims: %w", err) - } - if err := d.Set("snowflake_user_mapping_attribute", describeOutput.ExternalOauthSnowflakeUserMappingAttribute); err != nil { - return fmt.Errorf("error setting snowflake_user_mapping_attribute: %w", err) + + if v, ok := d.GetOk("external_oauth_scope_mapping_attribute"); ok { + req.WithExternalOauthScopeMappingAttribute(v.(string)) } - if err := d.Set("scope_mapping_attribute", describeOutput.ExternalOauthScopeMappingAttribute); err != nil { - return fmt.Errorf("error setting scope_mapping_attribute: %w", err) + + if err := client.SecurityIntegrations.CreateExternalOauth(ctx, req); err != nil { + return diag.FromErr(err) } - return err + d.SetId(name) + + return ReadContextExternalOauthIntegration(false)(ctx, d, meta) } -// UpdateExternalOauthIntegration implements schema.UpdateFunc. -func UpdateExternalOauthIntegration(d *schema.ResourceData, meta interface{}) error { - manager, err := snowflake.NewExternalOauthIntegration3Manager() - if err != nil { - return fmt.Errorf("couldn't create external oauth integration builder: %w", err) - } +func ReadContextExternalOauthIntegration(expectExternalChanges bool) schema.ReadContextFunc { + return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) - runAlter := false - alterInput := &snowflake.ExternalOauthIntegration3UpdateInput{ - ExternalOauthIntegration3: snowflake.ExternalOauthIntegration3{ - TopLevelIdentifier: snowflake.TopLevelIdentifier{ - Name: d.Get("name").(string), - }, - }, - } - runUnset := false - unsetInput := &snowflake.ExternalOauthIntegration3UpdateInput{ - ExternalOauthIntegration3: snowflake.ExternalOauthIntegration3{ - TopLevelIdentifier: snowflake.TopLevelIdentifier{ - Name: d.Get("name").(string), - }, - }, - } + integration, err := client.SecurityIntegrations.ShowByID(ctx, id) + if err != nil { + if errors.Is(err, sdk.ErrObjectNotFound) { + d.SetId("") + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Warning, + Summary: "Failed to query security integration. Marking the resource as removed.", + Detail: fmt.Sprintf("Security integration name: %s, Err: %s", id.FullyQualifiedName(), err), + }, + } + } + return diag.FromErr(err) + } - if d.HasChange("enabled") { - val, ok := d.GetOk("enabled") - if ok { - alterInput.Enabled = val.(bool) - alterInput.EnabledOk = true - runAlter = true - } else { - unsetInput.EnabledOk = true - runUnset = true + if c := integration.Category; c != sdk.SecurityIntegrationCategory { + return diag.FromErr(fmt.Errorf("expected %v to be a %s integration, got %v", id, sdk.SecurityIntegrationCategory, c)) } - } - if d.HasChange("type") { - val, ok := d.GetOk("type") - if ok { - alterInput.Type = val.(string) - alterInput.TypeOk = true - runAlter = true - } else { - unsetInput.TypeOk = true - runUnset = true + if err := d.Set("name", integration.Name); err != nil { + return diag.FromErr(err) } - } - if d.HasChange("issuer") { - val, ok := d.GetOk("issuer") - if ok { - alterInput.ExternalOauthIssuer = val.(string) - alterInput.ExternalOauthIssuerOk = true - runAlter = true - } else { - unsetInput.ExternalOauthIssuerOk = true - runUnset = true + if err := d.Set("comment", integration.Comment); err != nil { + return diag.FromErr(err) } - } - if d.HasChange("token_user_mapping_claims") { - val, ok := d.GetOk("token_user_mapping_claims") - if ok { - alterInput.ExternalOauthTokenUserMappingClaim = expandStringList(val.(*schema.Set).List()) - alterInput.ExternalOauthTokenUserMappingClaimOk = true - runAlter = true - } else { - unsetInput.ExternalOauthTokenUserMappingClaimOk = true - runUnset = true + + if err := d.Set("created_on", integration.CreatedOn.String()); err != nil { + return diag.FromErr(err) } - } - if d.HasChange("snowflake_user_mapping_attribute") { - val, ok := d.GetOk("snowflake_user_mapping_attribute") - if ok { - alterInput.ExternalOauthSnowflakeUserMappingAttribute = snowflake.SFUserMappingAttribute(val.(string)) - alterInput.ExternalOauthSnowflakeUserMappingAttributeOk = true - runAlter = true - } else { - unsetInput.ExternalOauthSnowflakeUserMappingAttributeOk = true - runUnset = true + + if err := d.Set("enabled", integration.Enabled); err != nil { + return diag.FromErr(err) } - } - if d.HasChange("jws_keys_urls") { - val, ok := d.GetOk("jws_keys_urls") - if ok { - alterInput.ExternalOauthJwsKeysURL = expandStringList(val.(*schema.Set).List()) - alterInput.ExternalOauthJwsKeysURLOk = true - runAlter = true - } else { - unsetInput.ExternalOauthJwsKeysURLOk = true - runUnset = true + subType, err := integration.SubType() + if err != nil { + return diag.FromErr(err) } - } - if d.HasChange("rsa_public_key") { - val, ok := d.GetOk("rsa_public_key") - if ok { - alterInput.ExternalOauthRsaPublicKey = val.(string) - alterInput.ExternalOauthRsaPublicKeyOk = true - runAlter = true - } else { - unsetInput.ExternalOauthRsaPublicKeyOk = true - runUnset = true + if err := d.Set("external_oauth_type", subType); err != nil { + return diag.FromErr(err) } - } - if d.HasChange("rsa_public_key_2") { - val, ok := d.GetOk("rsa_public_key_2") - if ok { - alterInput.ExternalOauthRsaPublicKey2 = val.(string) - alterInput.ExternalOauthRsaPublicKey2Ok = true - runAlter = true - } else { - unsetInput.ExternalOauthRsaPublicKey2Ok = true - runUnset = true + properties, err := client.SecurityIntegrations.Describe(ctx, id) + if err != nil { + return diag.FromErr(err) } - } - if d.HasChange("blocked_roles") { - val, ok := d.GetOk("blocked_roles") - if ok { - alterInput.ExternalOauthBlockedRolesList = expandStringList(val.(*schema.Set).List()) - alterInput.ExternalOauthBlockedRolesListOk = true - runAlter = true - } else { - unsetInput.ExternalOauthBlockedRolesListOk = true - runUnset = true + for _, property := range properties { + name := property.Name + value := property.Value + switch name { + case "COMMENT", "CREATED_ON", "ENABLED": + case "EXTERNAL_OAUTH_ALLOWED_ROLES_LIST": + var allowedRoles []string + if len(value) > 0 { + allowedRoles = strings.Split(value, ",") + } + if err := d.Set("external_oauth_allowed_roles_list", allowedRoles); err != nil { + return diag.FromErr(err) + } + case "EXTERNAL_OAUTH_ANY_ROLE_MODE": + if err := d.Set("external_oauth_any_role_mode", value); err != nil { + return diag.FromErr(err) + } + case "EXTERNAL_OAUTH_AUDIENCE_LIST": + var audienceList []string + if len(value) > 0 { + audienceList = strings.Split(value, ",") + } + if err := d.Set("external_oauth_audience_list", audienceList); err != nil { + return diag.FromErr(err) + } + case "EXTERNAL_OAUTH_BLOCKED_ROLES_LIST": + var blockedRoles []string + if len(value) > 0 { + blockedRoles = strings.Split(value, ",") + } + if err := d.Set("external_oauth_blocked_roles_list", blockedRoles); err != nil { + return diag.FromErr(err) + } + case "EXTERNAL_OAUTH_ISSUER": + if err := d.Set("external_oauth_issuer", value); err != nil { + return diag.FromErr(err) + } + case "EXTERNAL_OAUTH_JWS_KEYS_URL": + var audienceList []string + if len(value) > 0 { + audienceList = strings.Split(value, ",") + } + if err := d.Set("external_oauth_jws_keys_url", audienceList); err != nil { + return diag.FromErr(err) + } + case "EXTERNAL_OAUTH_RSA_PUBLIC_KEY": + if err := d.Set("external_oauth_rsa_public_key", value); err != nil { + return diag.FromErr(err) + } + case "EXTERNAL_OAUTH_RSA_PUBLIC_KEY_2": + if err := d.Set("external_oauth_rsa_public_key", value); err != nil { + return diag.FromErr(err) + } + case "EXTERNAL_OAUTH_SCOPE_DELIMITER": + if err := d.Set("external_oauth_scope_delimiter", value); err != nil { + return diag.FromErr(err) + } + case "EXTERNAL_OAUTH_SCOPE_MAPPING_ATTRIBUTE": + if err := d.Set("external_oauth_scope_mapping_attribute", value); err != nil { + return diag.FromErr(err) + } + case "EXTERNAL_OAUTH_SNOWFLAKE_USER_MAPPING_ATTRIBUTE": + if err := d.Set("external_oauth_snowflake_user_mapping_attribute", value); err != nil { + return diag.FromErr(err) + } + case "EXTERNAL_OAUTH_TOKEN_USER_MAPPING_CLAIM": + value = strings.TrimLeft(value, "[") + value = strings.TrimRight(value, "]") + elems := strings.Split(value, ",") + for i := range elems { + elems[i] = strings.Trim(elems[i], " '") + } + if value == "" { + elems = nil + } + + if err := d.Set("external_oauth_token_user_mapping_claims", elems); err != nil { + return diag.FromErr(err) + } + default: + log.Printf("[WARN] unexpected property %v returned from Snowflake", name) + } } - } - if d.HasChange("allowed_roles") { - val, ok := d.GetOk("allowed_roles") - if ok { - alterInput.ExternalOauthAllowedRolesList = expandStringList(val.(*schema.Set).List()) - alterInput.ExternalOauthAllowedRolesListOk = true - runAlter = true - } else { - unsetInput.ExternalOauthAllowedRolesListOk = true - runUnset = true + param := "EXTERNAL_OAUTH_ADD_PRIVILEGED_ROLES_TO_BLOCKED_LIST" + params, err := client.Parameters.ShowParameters(ctx, &sdk.ShowParametersOptions{ + Like: &sdk.Like{ + Pattern: sdk.Pointer(param), + }, + In: &sdk.ParametersIn{ + Account: sdk.Pointer(true), + }, + }) + if err != nil { + return diag.FromErr(err) } - } - if d.HasChange("audience_urls") { - val, ok := d.GetOk("audience_urls") - if ok { - alterInput.ExternalOauthAudienceList = expandStringList(val.(*schema.Set).List()) - alterInput.ExternalOauthAudienceListOk = true - runAlter = true - } else { - unsetInput.ExternalOauthAudienceListOk = true - runUnset = true + var found *sdk.Parameter + for _, v := range params { + if v.Key == param { + found = v + break + } + } + if found == nil { + return diag.FromErr(fmt.Errorf("parameter %s not found", param)) + } + paramVal := helpers.StringToBool(found.Value) + if !paramVal { + return nil + } + if err := d.Set("external_oauth_add_privileged_roles_to_blocked_list", paramVal); err != nil { + return diag.FromErr(err) } + return nil } - if d.HasChange("any_role_mode") { - val, ok := d.GetOk("any_role_mode") - if ok { - alterInput.ExternalOauthAnyRoleMode = snowflake.AnyRoleMode(val.(string)) - alterInput.ExternalOauthAnyRoleModeOk = true - runAlter = true - } else { - unsetInput.ExternalOauthAnyRoleModeOk = true - runUnset = true +} + +func UpdateContextExternalOauthIntegration(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) + set, unset := sdk.NewExternalOauthIntegrationSetRequest(), sdk.NewExternalOauthIntegrationUnsetRequest() + + if d.HasChange("comment") { + // set.WithComment(sdk.StringAllowEmpty{Value: d.Get("comment").(string)}) + } + + if d.HasChange("enabled") { + set.WithEnabled(d.Get("enabled").(bool)) + } + + if d.HasChange("external_oauth_allowed_roles_list") { + v := d.Get("external_oauth_allowed_roles_list").([]any) + allowedRoles := make([]sdk.AccountObjectIdentifier, len(v)) + for i := range v { + allowedRoles[i] = sdk.NewAccountObjectIdentifier(v[i].(string)) } + set.WithExternalOauthAllowedRolesList(sdk.AllowedRolesListRequest{AllowedRolesList: allowedRoles}) } - if d.HasChange("scope_delimiter") { - val, ok := d.GetOk("scope_delimiter") - if ok { - alterInput.ExternalOauthScopeDelimiter = val.(string) - alterInput.ExternalOauthScopeDelimiterOk = true - runAlter = true + + if d.HasChange("external_oauth_any_role_mode") { + v := d.Get("external_oauth_any_role_mode").(string) + if len(v) > 0 { + value, err := sdk.ToExternalOauthSecurityIntegrationAnyRoleModeOption(v) + if err != nil { + return diag.FromErr(err) + } + set.WithExternalOauthAnyRoleMode(value) } else { - unsetInput.ExternalOauthScopeDelimiterOk = true - runUnset = true + set.WithExternalOauthAnyRoleMode(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable) } } - if d.HasChange("scope_mapping_attribute") { - val, ok := d.GetOk("scope_mapping_attribute") - if ok { - alterInput.ExternalOauthScopeMappingAttribute = val.(string) - alterInput.ExternalOauthScopeMappingAttributeOk = true - runAlter = true + + if d.HasChange("external_oauth_audience_list") { + v := d.Get("external_oauth_audience_list").([]any) + if len(v) > 0 { + audienceList := make([]sdk.AudienceListItem, len(v)) + for i := range v { + audienceList[i] = sdk.AudienceListItem{Item: v[i].(string)} + } + set.WithExternalOauthAudienceList(sdk.AudienceListRequest{AudienceList: audienceList}) } else { - unsetInput.ExternalOauthScopeMappingAttributeOk = true - runUnset = true + unset.WithExternalOauthAudienceList(true) } } - if d.HasChange("comment") { - val, ok := d.GetOk("comment") - if ok { - alterInput.Comment.String = val.(string) - alterInput.CommentOk = true - runAlter = true - } else { - unsetInput.CommentOk = true - runUnset = true + + if d.HasChange("external_oauth_blocked_roles_list") { + v := d.Get("external_oauth_blocked_roles_list").([]any) + blockedRoles := make([]sdk.AccountObjectIdentifier, len(v)) + for i := range v { + blockedRoles[i] = sdk.NewAccountObjectIdentifier(v[i].(string)) } + set.WithExternalOauthBlockedRolesList(sdk.BlockedRolesListRequest{BlockedRolesList: blockedRoles}) } - client := meta.(*provider.Context).Client - db := client.GetConn().DB + if d.HasChange("external_oauth_issuer") { + set.WithExternalOauthIssuer(d.Get("external_oauth_issuer").(string)) + } - if runAlter { - stmt, err := manager.Update(alterInput) - if err != nil { - return fmt.Errorf("couldn't generate alter statement for external oauth integration: %w", err) + if d.HasChange("external_oauth_jws_keys_url") { + v := d.Get("external_oauth_jws_keys_url").([]any) + if len(v) > 0 { + urls := make([]sdk.JwsKeysUrl, len(v)) + for i := range v { + urls[i] = sdk.JwsKeysUrl{JwsKeyUrl: v[i].(string)} + } + set.WithExternalOauthJwsKeysUrl(urls) } + } - err = snowflake.Exec(db, stmt) - if err != nil { - return fmt.Errorf("error executing alter statement: %w", err) - } + if d.HasChange("external_oauth_rsa_public_key") { + set.WithExternalOauthRsaPublicKey(d.Get("external_oauth_rsa_public_key").(string)) } - if runUnset { - stmt, err := manager.Unset(unsetInput) - if err != nil { - return fmt.Errorf("couldn't generate unset statement for external oauth integration: %w", err) - } + if d.HasChange("external_oauth_rsa_public_key_2") { + set.WithExternalOauthRsaPublicKey2(d.Get("external_oauth_rsa_public_key_2").(string)) + } - err = snowflake.Exec(db, stmt) - if err != nil { - return fmt.Errorf("error executing unset statement: %w", err) + if d.HasChange("external_oauth_scope_delimiter") { + if v, ok := d.GetOk("external_oauth_scope_delimiter"); ok { + set.WithExternalOauthScopeDelimiter(v.(string)) + } else { + set.WithExternalOauthScopeDelimiter(",") } } - return ReadExternalOauthIntegration(d, meta) -} + if d.HasChange("external_oauth_scope_mapping_attribute") { + // set.WithExternalOauthScopeMappingAttribute(d.Get("external_oauth_scope_mapping_attribute").(string)) + } -// DeleteExternalOauthIntegration implements schema.DeleteFunc. -func DeleteExternalOauthIntegration(d *schema.ResourceData, meta interface{}) error { - manager, err := snowflake.NewExternalOauthIntegration3Manager() - if err != nil { - return fmt.Errorf("couldn't create external oauth integration builder: %w", err) + if d.HasChange("external_oauth_snowflake_user_mapping_attribute") { + v := d.Get("external_oauth_snowflake_user_mapping_attribute").(string) + if len(v) > 0 { + value, err := sdk.ToExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeOption(v) + if err != nil { + return diag.FromErr(err) + } + set.WithExternalOauthSnowflakeUserMappingAttribute(value) + } } - input := &snowflake.ExternalOauthIntegration3DeleteInput{ - TopLevelIdentifier: snowflake.TopLevelIdentifier{ - Name: d.Get("name").(string), - }, + if d.HasChange("external_oauth_token_user_mapping_claims") { + v := d.Get("external_oauth_token_user_mapping_claims").([]any) + claims := make([]sdk.TokenUserMappingClaim, len(v)) + for i := range v { + claims[i] = sdk.TokenUserMappingClaim{ + Claim: v[i].(string), + } + } + set.WithExternalOauthTokenUserMappingClaim(claims) } - stmt, err := manager.Delete(input) - if err != nil { - return fmt.Errorf("couldn't generate drop statement: %w", err) + if d.HasChange("external_oauth_type") { + v := d.Get("external_oauth_type").(string) + if len(v) > 0 { + value, err := sdk.ToExternalOauthSecurityIntegrationTypeOption(v) + if err != nil { + return diag.FromErr(err) + } + set.WithExternalOauthType(value) + } } + if !reflect.DeepEqual(*set, sdk.ExternalOauthIntegrationSetRequest{}) { + if err := client.SecurityIntegrations.AlterExternalOauth(ctx, sdk.NewAlterExternalOauthSecurityIntegrationRequest(id).WithSet(*set)); err != nil { + return diag.FromErr(err) + } + } + if !reflect.DeepEqual(*unset, sdk.ExternalOauthIntegrationUnsetRequest{}) { + if err := client.SecurityIntegrations.AlterExternalOauth(ctx, sdk.NewAlterExternalOauthSecurityIntegrationRequest(id).WithUnset(*unset)); err != nil { + return diag.FromErr(err) + } + } + return ReadContextExternalOauthIntegration(false)(ctx, d, meta) +} + +func DeleteContextExternalOauthIntegration(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) client := meta.(*provider.Context).Client - db := client.GetConn().DB - err = snowflake.Exec(db, stmt) + + err := client.SecurityIntegrations.Drop(ctx, sdk.NewDropSecurityIntegrationRequest(sdk.NewAccountObjectIdentifier(id.Name())).WithIfExists(true)) if err != nil { - return fmt.Errorf("error executing drop statement: %w", err) + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Error deleting integration", + Detail: fmt.Sprintf("id %v err = %v", id.Name(), err), + }, + } } + d.SetId("") return nil } - -func ExternalOauthIntegrationID(eoi *snowflake.ExternalOauthIntegration3) string { - return eoi.QualifiedName() -} - -func ExternalOauthIntegrationIdentifier(id string) *snowflake.TopLevelIdentifier { - return snowflake.TopLevelIdentifierFromQualifiedName(id) -} diff --git a/pkg/resources/external_oauth_integration_acceptance_test.go b/pkg/resources/external_oauth_integration_acceptance_test.go index fc227bcab2..c4f91e612f 100644 --- a/pkg/resources/external_oauth_integration_acceptance_test.go +++ b/pkg/resources/external_oauth_integration_acceptance_test.go @@ -1,117 +1,76 @@ package resources_test import ( - "fmt" - "strings" "testing" acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/helpers/random" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" - "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-testing/config" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/tfversion" ) -func TestAcc_ExternalOauthIntegration(t *testing.T) { - oauthIntName := acc.TestClient().Ids.Alpha() - integrationType := "AZURE" - - issuer := fmt.Sprintf("https://sts.windows.net/%s", uuid.NewString()) - - resource.Test(t, resource.TestCase{ - ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, - PreCheck: func() { acc.TestAccPreCheck(t) }, - TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.RequireAbove(tfversion.Version1_5_0), - }, - CheckDestroy: nil, - Steps: []resource.TestStep{ - { - Config: externalOauthIntegrationConfig(oauthIntName, integrationType, issuer, "test resource"), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "name", oauthIntName), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "type", integrationType), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "enabled", "true"), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "issuer", issuer), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "snowflake_user_mapping_attribute", "LOGIN_NAME"), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "token_user_mapping_claims.#", "2"), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "token_user_mapping_claims.0", "test"), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "token_user_mapping_claims.1", "upn"), - ), - }, - { - ResourceName: "snowflake_external_oauth_integration.test", - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - -func TestAcc_ExternalOauthIntegrationEmptyComment(t *testing.T) { - oauthIntName := strings.ToLower(acc.TestClient().Ids.Alpha()) - integrationType := "AZURE" - - issuer := fmt.Sprintf("https://sts.windows.net/%s", uuid.NewString()) - - resource.Test(t, resource.TestCase{ - ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, - PreCheck: func() { acc.TestAccPreCheck(t) }, - TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.RequireAbove(tfversion.Version1_5_0), - }, - CheckDestroy: nil, - Steps: []resource.TestStep{ - { - Config: externalOauthIntegrationConfig(oauthIntName, integrationType, issuer, ""), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "name", oauthIntName), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "type", integrationType), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "enabled", "true"), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "issuer", issuer), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "snowflake_user_mapping_attribute", "LOGIN_NAME"), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "token_user_mapping_claims.#", "2"), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "token_user_mapping_claims.0", "test"), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "token_user_mapping_claims.1", "upn"), - ), - }, - { - ResourceName: "snowflake_external_oauth_integration.test", - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - -func TestAcc_ExternalOauthIntegrationLowercaseName(t *testing.T) { - oauthIntName := strings.ToLower(acc.TestClient().Ids.Alpha()) - integrationType := "AZURE" - - issuer := fmt.Sprintf("https://sts.windows.net/%s", uuid.NewString()) - +func TestAcc_ExternalOauthIntegration_completeWithJwsKeysUrlAndAllowedRolesList(t *testing.T) { + id := acc.TestClient().Ids.RandomAccountObjectIdentifier() + role1, role1Cleanup := acc.TestClient().Role.CreateRole(t) + issuer := random.String() + t.Cleanup(role1Cleanup) + + m := func() map[string]config.Variable { + return map[string]config.Variable{ + "comment": config.StringVariable("foo"), + "enabled": config.BoolVariable(true), + "external_oauth_add_privileged_roles_to_blocked_list": config.BoolVariable(true), + "external_oauth_allowed_roles_list": config.SetVariable(config.StringVariable(role1.ID().Name())), + "external_oauth_any_role_mode": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), + "external_oauth_audience_list": config.SetVariable(config.StringVariable("foo")), + "external_oauth_issuer": config.StringVariable(issuer), + "external_oauth_jws_keys_url": config.SetVariable(config.StringVariable("https://example.com")), + "external_oauth_scope_delimiter": config.StringVariable("."), + "external_oauth_scope_mapping_attribute": config.StringVariable("foo"), + "external_oauth_snowflake_user_mapping_attribute": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), + "external_oauth_token_user_mapping_claims": config.SetVariable(config.StringVariable("foo")), + "name": config.StringVariable(id.Name()), + "type": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), + } + } resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, PreCheck: func() { acc.TestAccPreCheck(t) }, TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.RequireAbove(tfversion.Version1_5_0), }, - CheckDestroy: nil, Steps: []resource.TestStep{ { - Config: externalOauthIntegrationConfig(oauthIntName, integrationType, issuer, "test resource"), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "name", oauthIntName), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "type", integrationType), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrl"), + ConfigVariables: m(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "comment", "foo"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "enabled", "true"), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "issuer", issuer), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "snowflake_user_mapping_attribute", "LOGIN_NAME"), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "token_user_mapping_claims.#", "2"), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "token_user_mapping_claims.0", "test"), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "token_user_mapping_claims.1", "upn"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_allowed_roles_list.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_allowed_roles_list.0", role1.ID().Name()), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_any_role_mode", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_audience_list.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_audience_list.0", "foo"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_issuer", issuer), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_jws_keys_url.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_jws_keys_url.0", "https://example.com"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_scope_delimiter", "."), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_scope_mapping_attribute", "foo"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_snowflake_user_mapping_attribute", string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_token_user_mapping_claims.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_token_user_mapping_claims.0", "foo"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "name", id.Name()), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_type", string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), + resource.TestCheckResourceAttrSet("snowflake_external_oauth_integration.test", "created_on"), ), }, { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrl"), + ConfigVariables: m(), ResourceName: "snowflake_external_oauth_integration.test", ImportState: true, ImportStateVerify: true, @@ -120,68 +79,42 @@ func TestAcc_ExternalOauthIntegrationLowercaseName(t *testing.T) { }) } -func TestAcc_ExternalOauthIntegrationCustom(t *testing.T) { - oauthIntName := acc.TestClient().Ids.Alpha() - integrationType := "CUSTOM" - - issuer := fmt.Sprintf("https://sts.windows.net/%s", uuid.NewString()) - +func TestAcc_ExternalOauthIntegration_invalid(t *testing.T) { + m := func() map[string]config.Variable { + return map[string]config.Variable{ + "comment": config.StringVariable("foo"), + "enabled": config.BoolVariable(true), + "external_oauth_add_privileged_roles_to_blocked_list": config.BoolVariable(true), + "external_oauth_allowed_roles_list": config.SetVariable(config.StringVariable("foo")), + "external_oauth_any_role_mode": config.StringVariable("foo"), + "external_oauth_audience_list": config.SetVariable(config.StringVariable("foo")), + "external_oauth_blocked_roles_list": config.SetVariable(config.StringVariable("foo")), + "external_oauth_issuer": config.StringVariable("foo"), + "external_oauth_jws_keys_url": config.SetVariable(config.StringVariable("foo")), + "external_oauth_rsa_public_key": config.StringVariable("foo"), + "external_oauth_rsa_public_key_2": config.StringVariable("foo"), + "external_oauth_scope_delimiter": config.StringVariable("foo"), + "external_oauth_scope_mapping_attribute": config.StringVariable("foo"), + "external_oauth_snowflake_user_mapping_attribute": config.StringVariable("foo"), + "external_oauth_token_user_mapping_claims": config.SetVariable(config.StringVariable("foo")), + "name": config.StringVariable("foo"), + "type": config.StringVariable("foo"), + } + } resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, PreCheck: func() { acc.TestAccPreCheck(t) }, TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.RequireAbove(tfversion.Version1_5_0), }, - CheckDestroy: nil, Steps: []resource.TestStep{ { - Config: fmt.Sprintf(` - resource "snowflake_external_oauth_integration" "test" { - name = "%s" - type = "%s" - enabled = true - issuer = "%s" - snowflake_user_mapping_attribute = "LOGIN_NAME" - jws_keys_urls = ["https://login.windows.net/common/discovery/keys"] - audience_urls = ["https://analysis.windows.net/powerbi/connector/Snowflake"] - token_user_mapping_claims = ["upn", "test"] - scope_mapping_attribute = "scp" - comment = "hey" - } - `, oauthIntName, integrationType, issuer), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "name", oauthIntName), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "type", integrationType), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "enabled", "true"), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "issuer", issuer), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "snowflake_user_mapping_attribute", "LOGIN_NAME"), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "scope_mapping_attribute", "scp"), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "token_user_mapping_claims.#", "2"), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "token_user_mapping_claims.0", "test"), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "token_user_mapping_claims.1", "upn"), - ), - }, - { - ResourceName: "snowflake_external_oauth_integration.test", - ImportState: true, - ImportStateVerify: true, + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/complete"), + ConfigVariables: m(), + ExpectError: helpers.MatchAllStringsInOrderNonOverlapping([]string{ + // TODO: Implement + }), }, }, }) } - -func externalOauthIntegrationConfig(name, integrationType, issuer, comment string) string { - return fmt.Sprintf(` - resource "snowflake_external_oauth_integration" "test" { - name = "%s" - type = "%s" - enabled = true - issuer = "%s" - snowflake_user_mapping_attribute = "LOGIN_NAME" - jws_keys_urls = ["https://login.windows.net/common/discovery/keys"] - audience_urls = ["https://analysis.windows.net/powerbi/connector/Snowflake"] - token_user_mapping_claims = ["upn", "test"] - comment = "%s" - } - `, name, integrationType, issuer, comment) -} diff --git a/pkg/resources/helper_expansion.go b/pkg/resources/helper_expansion.go index 82a00efcad..41e40b7b5d 100644 --- a/pkg/resources/helper_expansion.go +++ b/pkg/resources/helper_expansion.go @@ -2,6 +2,8 @@ package resources import ( "slices" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" ) // borrowed from https://github.com/terraform-providers/terraform-provider-aws/blob/master/aws/structure.go#L924:6 @@ -27,6 +29,18 @@ func expandStringList(configured []interface{}) []string { return vs } +// TODO: unit tests, maybe do with transform func +func expandObjectIdentifierList(configured []interface{}) []sdk.AccountObjectIdentifier { + vs := make([]sdk.AccountObjectIdentifier, 0, len(configured)) + for _, v := range configured { + val, ok := v.(string) + if ok && val != "" { + vs = append(vs, sdk.NewAccountObjectIdentifier(val)) + } + } + return vs +} + func expandStringListAllowEmpty(configured []interface{}) []string { // Allow empty values during expansion vs := make([]string, 0, len(configured)) diff --git a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/basic/test.tf b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/basic/test.tf new file mode 100644 index 0000000000..aaa1f06f75 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/basic/test.tf @@ -0,0 +1,19 @@ +resource "snowflake_external_oauth_integration" "test" { + comment = var.comment + enabled = var.enabled + external_oauth_add_privileged_roles_to_blocked_list = var.external_oauth_add_privileged_roles_to_blocked_list + external_oauth_allowed_roles_list = var.external_oauth_allowed_roles_list + external_oauth_any_role_mode = var.external_oauth_any_role_mode + external_oauth_audience_list = var.external_oauth_audience_list + external_oauth_blocked_roles_list = var.external_oauth_blocked_roles_list + external_oauth_issuer = var.external_oauth_issuer + external_oauth_jws_keys_url = var.external_oauth_jws_keys_url + external_oauth_rsa_public_key = var.external_oauth_rsa_public_key + external_oauth_rsa_public_key_2 = var.external_oauth_rsa_public_key_2 + external_oauth_scope_delimiter = var.external_oauth_scope_delimiter + external_oauth_scope_mapping_attribute = var.external_oauth_scope_mapping_attribute + external_oauth_snowflake_user_mapping_attribute = var.external_oauth_snowflake_user_mapping_attribute + external_oauth_token_user_mapping_claims = var.external_oauth_token_user_mapping_claims + name = var.name + type = var.type +} diff --git a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/basic/variables.tf b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/basic/variables.tf new file mode 100644 index 0000000000..4410ac597f --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/basic/variables.tf @@ -0,0 +1,52 @@ + +variable "comment" { + type = string +} +variable "enabled" { + type = bool +} +variable "external_oauth_add_privileged_roles_to_blocked_list" { + type = bool +} +variable "external_oauth_allowed_roles_list" { + type = set +} +variable "external_oauth_any_role_mode" { + type = string +} +variable "external_oauth_audience_list" { + type = set +} +variable "external_oauth_blocked_roles_list" { + type = set +} +variable "external_oauth_issuer" { + type = string +} +variable "external_oauth_jws_keys_url" { + type = set +} +variable "external_oauth_rsa_public_key" { + type = string +} +variable "external_oauth_rsa_public_key_2" { + type = string +} +variable "external_oauth_scope_delimiter" { + type = string +} +variable "external_oauth_scope_mapping_attribute" { + type = string +} +variable "external_oauth_snowflake_user_mapping_attribute" { + type = string +} +variable "external_oauth_token_user_mapping_claims" { + type = set +} +variable "name" { + type = string +} +variable "type" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrl/test.tf b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrl/test.tf new file mode 100644 index 0000000000..eef7e07af3 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrl/test.tf @@ -0,0 +1,15 @@ +resource "snowflake_external_oauth_integration" "test" { + comment = var.comment + enabled = var.enabled + external_oauth_allowed_roles_list = var.external_oauth_allowed_roles_list + external_oauth_any_role_mode = var.external_oauth_any_role_mode + external_oauth_audience_list = var.external_oauth_audience_list + external_oauth_issuer = var.external_oauth_issuer + external_oauth_jws_keys_url = var.external_oauth_jws_keys_url + external_oauth_scope_delimiter = var.external_oauth_scope_delimiter + external_oauth_scope_mapping_attribute = var.external_oauth_scope_mapping_attribute + external_oauth_snowflake_user_mapping_attribute = var.external_oauth_snowflake_user_mapping_attribute + external_oauth_token_user_mapping_claims = var.external_oauth_token_user_mapping_claims + name = var.name + external_oauth_type = var.type +} diff --git a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrl/variables.tf b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrl/variables.tf new file mode 100644 index 0000000000..7d86487c9e --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrl/variables.tf @@ -0,0 +1,40 @@ + +variable "comment" { + type = string +} +variable "enabled" { + type = bool +} +variable "external_oauth_allowed_roles_list" { + type = set(string) +} +variable "external_oauth_any_role_mode" { + type = string +} +variable "external_oauth_audience_list" { + type = set(string) +} +variable "external_oauth_issuer" { + type = string +} +variable "external_oauth_jws_keys_url" { + type = set(string) +} +variable "external_oauth_scope_delimiter" { + type = string +} +variable "external_oauth_scope_mapping_attribute" { + type = string +} +variable "external_oauth_snowflake_user_mapping_attribute" { + type = string +} +variable "external_oauth_token_user_mapping_claims" { + type = set(string) +} +variable "name" { + type = string +} +variable "type" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/invalid/test.tf b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/invalid/test.tf new file mode 100644 index 0000000000..aaa1f06f75 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/invalid/test.tf @@ -0,0 +1,19 @@ +resource "snowflake_external_oauth_integration" "test" { + comment = var.comment + enabled = var.enabled + external_oauth_add_privileged_roles_to_blocked_list = var.external_oauth_add_privileged_roles_to_blocked_list + external_oauth_allowed_roles_list = var.external_oauth_allowed_roles_list + external_oauth_any_role_mode = var.external_oauth_any_role_mode + external_oauth_audience_list = var.external_oauth_audience_list + external_oauth_blocked_roles_list = var.external_oauth_blocked_roles_list + external_oauth_issuer = var.external_oauth_issuer + external_oauth_jws_keys_url = var.external_oauth_jws_keys_url + external_oauth_rsa_public_key = var.external_oauth_rsa_public_key + external_oauth_rsa_public_key_2 = var.external_oauth_rsa_public_key_2 + external_oauth_scope_delimiter = var.external_oauth_scope_delimiter + external_oauth_scope_mapping_attribute = var.external_oauth_scope_mapping_attribute + external_oauth_snowflake_user_mapping_attribute = var.external_oauth_snowflake_user_mapping_attribute + external_oauth_token_user_mapping_claims = var.external_oauth_token_user_mapping_claims + name = var.name + type = var.type +} diff --git a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/invalid/variables.tf b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/invalid/variables.tf new file mode 100644 index 0000000000..4410ac597f --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/invalid/variables.tf @@ -0,0 +1,52 @@ + +variable "comment" { + type = string +} +variable "enabled" { + type = bool +} +variable "external_oauth_add_privileged_roles_to_blocked_list" { + type = bool +} +variable "external_oauth_allowed_roles_list" { + type = set +} +variable "external_oauth_any_role_mode" { + type = string +} +variable "external_oauth_audience_list" { + type = set +} +variable "external_oauth_blocked_roles_list" { + type = set +} +variable "external_oauth_issuer" { + type = string +} +variable "external_oauth_jws_keys_url" { + type = set +} +variable "external_oauth_rsa_public_key" { + type = string +} +variable "external_oauth_rsa_public_key_2" { + type = string +} +variable "external_oauth_scope_delimiter" { + type = string +} +variable "external_oauth_scope_mapping_attribute" { + type = string +} +variable "external_oauth_snowflake_user_mapping_attribute" { + type = string +} +variable "external_oauth_token_user_mapping_claims" { + type = set +} +variable "name" { + type = string +} +variable "type" { + type = string +} diff --git a/pkg/sdk/security_integrations_def.go b/pkg/sdk/security_integrations_def.go index 8c87adb64c..8453a8df51 100644 --- a/pkg/sdk/security_integrations_def.go +++ b/pkg/sdk/security_integrations_def.go @@ -26,6 +26,29 @@ const ( ExternalOauthSecurityIntegrationTypeCustom ExternalOauthSecurityIntegrationTypeOption = "CUSTOM" ) +var AllExternalOauthSecurityIntegrationTypes = []ExternalOauthSecurityIntegrationTypeOption{ + ExternalOauthSecurityIntegrationTypeOkta, + ExternalOauthSecurityIntegrationTypeAzure, + ExternalOauthSecurityIntegrationTypePingFederate, + ExternalOauthSecurityIntegrationTypeCustom, +} + +func ToExternalOauthSecurityIntegrationTypeOption(s string) (ExternalOauthSecurityIntegrationTypeOption, error) { + s = strings.ToUpper(s) + switch s { + case string(ExternalOauthSecurityIntegrationTypeOkta): + return ExternalOauthSecurityIntegrationTypeOkta, nil + case string(ExternalOauthSecurityIntegrationTypeAzure): + return ExternalOauthSecurityIntegrationTypeAzure, nil + case string(ExternalOauthSecurityIntegrationTypePingFederate): + return ExternalOauthSecurityIntegrationTypePingFederate, nil + case string(ExternalOauthSecurityIntegrationTypeCustom): + return ExternalOauthSecurityIntegrationTypeCustom, nil + default: + return "", fmt.Errorf("invalid ExternalOauthSecurityIntegrationTypeOption: %s", s) + } +} + type ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeOption string const ( @@ -33,6 +56,23 @@ const ( ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeOption = "EMAIL_ADDRESS" ) +var AllExternalOauthSecurityIntegrationSnowflakeUserMappingAttributes = []ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeOption{ + ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeLoginName, + ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress, +} + +func ToExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeOption(s string) (ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeOption, error) { + s = strings.ToUpper(s) + switch s { + case string(ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeLoginName): + return ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeLoginName, nil + case string(ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress): + return ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress, nil + default: + return "", fmt.Errorf("invalid ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeOption: %s", s) + } +} + type ExternalOauthSecurityIntegrationAnyRoleModeOption string const ( @@ -41,6 +81,26 @@ const ( ExternalOauthSecurityIntegrationAnyRoleModeEnableForPrivilege ExternalOauthSecurityIntegrationAnyRoleModeOption = "ENABLE_FOR_PRIVILEGE" ) +var AllExternalOauthSecurityIntegrationAnyRoleModes = []ExternalOauthSecurityIntegrationAnyRoleModeOption{ + ExternalOauthSecurityIntegrationAnyRoleModeDisable, + ExternalOauthSecurityIntegrationAnyRoleModeEnable, + ExternalOauthSecurityIntegrationAnyRoleModeEnableForPrivilege, +} + +func ToExternalOauthSecurityIntegrationAnyRoleModeOption(s string) (ExternalOauthSecurityIntegrationAnyRoleModeOption, error) { + s = strings.ToUpper(s) + switch s { + case string(ExternalOauthSecurityIntegrationAnyRoleModeDisable): + return ExternalOauthSecurityIntegrationAnyRoleModeDisable, nil + case string(ExternalOauthSecurityIntegrationAnyRoleModeEnable): + return ExternalOauthSecurityIntegrationAnyRoleModeEnable, nil + case string(ExternalOauthSecurityIntegrationAnyRoleModeEnableForPrivilege): + return ExternalOauthSecurityIntegrationAnyRoleModeEnableForPrivilege, nil + default: + return "", fmt.Errorf("invalid ExternalOauthSecurityIntegrationAnyRoleModeOption: %s", s) + } +} + type OauthSecurityIntegrationUseSecondaryRolesOption string const ( From f0684978f92646a8495e71add9f940eab9ca6112 Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Wed, 3 Jul 2024 08:16:41 +0200 Subject: [PATCH 03/14] external oauth v1 conventions --- .../resource.tf | 8 +- pkg/resources/common.go | 26 + pkg/resources/custom_diffs.go | 2 - pkg/resources/external_oauth_integration.go | 444 +++++++++++++----- ...ernal_oauth_integration_acceptance_test.go | 22 +- .../basic/test.tf | 23 +- .../basic/variables.tf | 54 +-- .../test.tf | 2 +- .../variables.tf | 2 +- .../test.tf | 16 + .../variables.tf | 42 ++ 11 files changed, 432 insertions(+), 209 deletions(-) rename pkg/resources/testdata/TestAcc_ExternalOauthIntegration/{completeWithJwsKeysUrl => completeWithJwsKeysUrlAndAllowedRolesList}/test.tf (91%) rename pkg/resources/testdata/TestAcc_ExternalOauthIntegration/{completeWithJwsKeysUrl => completeWithJwsKeysUrlAndAllowedRolesList}/variables.tf (92%) create mode 100644 pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithRsaPublicKeysAndBlockedRolesList/test.tf create mode 100644 pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithRsaPublicKeysAndBlockedRolesList/variables.tf diff --git a/examples/resources/snowflake_external_oauth_integration/resource.tf b/examples/resources/snowflake_external_oauth_integration/resource.tf index bf3f9c0f88..08df19b063 100644 --- a/examples/resources/snowflake_external_oauth_integration/resource.tf +++ b/examples/resources/snowflake_external_oauth_integration/resource.tf @@ -1,10 +1,10 @@ # basic resource resource "snowflake_external_oauth_integration" "test" { enabled = true - external_oauth_issuer = "foo" + external_oauth_issuer = "issuer" external_oauth_snowflake_user_mapping_attribute = "LOGIN_NAME" external_oauth_token_user_mapping_claims = ["foo"] - name = "foo" + name = "test" external_oauth_type = "CUSTOM" } # resource with all fields set (jws keys url flow) @@ -15,7 +15,7 @@ resource "snowflake_external_oauth_integration" "test" { external_oauth_any_role_mode = "ENABLED" external_oauth_audience_list = ["foo"] external_oauth_blocked_roles_list = ["bar"] - external_oauth_issuer = "foo" + external_oauth_issuer = "issuer" external_oauth_jws_keys_url = ["https://example.com"] external_oauth_scope_delimiter = "," external_oauth_scope_mapping_attribute = "LOGIN_NAME" @@ -32,7 +32,7 @@ resource "snowflake_external_oauth_integration" "test" { external_oauth_any_role_mode = "ENABLED" external_oauth_audience_list = ["foo"] external_oauth_blocked_roles_list = ["bar"] - external_oauth_issuer = "foo" + external_oauth_issuer = "issuer" external_oauth_rsa_public_key = file("key.pem") external_oauth_rsa_public_key_2 = file("key2.pem") external_oauth_scope_delimiter = "foo" diff --git a/pkg/resources/common.go b/pkg/resources/common.go index d2c40fa7ca..2591a4bcf6 100644 --- a/pkg/resources/common.go +++ b/pkg/resources/common.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" + "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -80,3 +81,28 @@ func RSAKeyHash(key string) (string, error) { hash := sha256.Sum256(pubKeyBytes) return fmt.Sprintf("SHA256:%s", base64.StdEncoding.EncodeToString(hash[:])), nil } + +func listValueToSlice(value string, trimQuotes bool) []string { + value = strings.TrimLeft(value, "[") + value = strings.TrimRight(value, "]") + if value == "" { + return nil + } + elems := strings.Split(value, ",") + for i := range elems { + if trimQuotes { + elems[i] = strings.Trim(elems[i], " '") + } else { + elems[i] = strings.Trim(elems[i], " ") + } + } + return elems +} + +func ctyValToSliceString(valueElems []cty.Value) []string { + elems := make([]string, len(valueElems)) + for i, v := range valueElems { + elems[i] = v.AsString() + } + return elems +} diff --git a/pkg/resources/custom_diffs.go b/pkg/resources/custom_diffs.go index bb0bac0743..d6876432dc 100644 --- a/pkg/resources/custom_diffs.go +++ b/pkg/resources/custom_diffs.go @@ -11,8 +11,6 @@ import ( "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" diff --git a/pkg/resources/external_oauth_integration.go b/pkg/resources/external_oauth_integration.go index 6d0ef97dca..1c0e52fac6 100644 --- a/pkg/resources/external_oauth_integration.go +++ b/pkg/resources/external_oauth_integration.go @@ -4,12 +4,14 @@ import ( "context" "errors" "fmt" - "log" "reflect" "strings" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/logging" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" + "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" @@ -47,7 +49,7 @@ var oauthExternalIntegrationSchema = map[string]*schema.Schema{ Required: true, Description: "Specifies the URL to define the OAuth 2.0 authorization server.", }, - "external_oauth_token_user_mapping_claims": { + "external_oauth_token_user_mapping_claim": { Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, Required: true, @@ -70,7 +72,7 @@ var oauthExternalIntegrationSchema = map[string]*schema.Schema{ Elem: &schema.Schema{Type: schema.TypeString}, MaxItems: 3, Optional: true, - ConflictsWith: []string{"external_oauth_rsa_public_key"}, + ConflictsWith: []string{"external_oauth_rsa_public_key", "external_oauth_rsa_public_key_2"}, Description: "Specifies the endpoint or a list of endpoints from which to download public keys or certificates to validate an External OAuth access token. The maximum number of URLs that can be specified in the list is 3.", }, "external_oauth_rsa_public_key": { @@ -89,7 +91,6 @@ var oauthExternalIntegrationSchema = map[string]*schema.Schema{ Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, Optional: true, - Computed: true, Description: "Specifies the list of roles that a client cannot set as the primary role. By default, this list includes the ACCOUNTADMIN, ORGADMIN, and SECURITYADMIN roles. To remove these privileged roles from the list, use the ALTER ACCOUNT command to set the EXTERNAL_OAUTH_ADD_PRIVILEGED_ROLES_TO_BLOCKED_LIST account parameter to FALSE.", DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { value := d.Get("external_oauth_add_privileged_roles_to_blocked_list").(bool) @@ -116,8 +117,7 @@ var oauthExternalIntegrationSchema = map[string]*schema.Schema{ "external_oauth_any_role_mode": { Type: schema.TypeString, Optional: true, - Computed: true, - Description: "Specifies whether the OAuth client or user can use a role that is not defined in the OAuth access token.", + Description: fmt.Sprintf("Specifies whether the OAuth client or user can use a role that is not defined in the OAuth access token. Valid options are: %v", sdk.AsStringList(sdk.AllExternalOauthSecurityIntegrationAnyRoleModes)), ValidateFunc: validation.StringInSlice(sdk.AsStringList(sdk.AllExternalOauthSecurityIntegrationAnyRoleModes), true), DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { normalize := func(s string) string { @@ -129,7 +129,6 @@ var oauthExternalIntegrationSchema = map[string]*schema.Schema{ "external_oauth_scope_delimiter": { Type: schema.TypeString, Optional: true, - Computed: true, Description: "Specifies the scope delimiter in the authorization token.", }, "external_oauth_scope_mapping_attribute": { @@ -142,14 +141,21 @@ var oauthExternalIntegrationSchema = map[string]*schema.Schema{ Optional: true, Description: "Specifies a comment for the OAuth integration.", }, - "created_on": { - Type: schema.TypeString, + ShowOutputAttributeName: { + Type: schema.TypeList, Computed: true, - Description: "Date and time when the External OAUTH integration was created.", + Description: "Outputs the result of `SHOW SECURITY INTEGRATIONS` for the given security integration.", + Elem: &schema.Resource{ + Schema: schemas.ShowSecurityIntegrationSchema, + }, }, - "external_oauth_add_privileged_roles_to_blocked_list": { - Type: schema.TypeBool, - Computed: true, + DescribeOutputAttributeName: { + Type: schema.TypeList, + Computed: true, + Description: "Outputs the result of `DESCRIBE SECURITY INTEGRATIONS` for the given security integration.", + Elem: &schema.Resource{ + Schema: schemas.DescribeExternalOauthSecurityIntegrationSchema, + }, }, } @@ -166,29 +172,143 @@ func ExternalOauthIntegration() *schema.Resource { ForceNewIfChangeToEmptyString("external_oauth_rsa_public_key_2"), ForceNewIfChangeToEmptyString("external_oauth_scope_mapping_attribute"), ForceNewIfChangeToEmptySet[any]("external_oauth_jws_keys_url"), - ModifyStateIfParameterSet("external_oauth_blocked_roles_list", "EXTERNAL_OAUTH_ADD_PRIVILEGED_ROLES_TO_BLOCKED_LIST", func(d *schema.ResourceDiff) error { - allowed := d.Get("external_oauth_allowed_roles_list").(*schema.Set) - if allowed.Len() > 0 { - return nil - } - set := d.Get("external_oauth_blocked_roles_list").(*schema.Set) - set.Add("ACCOUNTADMIN") - set.Add("SECURITYADMIN") - return d.SetNew("external_oauth_blocked_roles_list", set) - }), + // ModifyStateIfParameterSet("external_oauth_blocked_roles_list", "EXTERNAL_OAUTH_ADD_PRIVILEGED_ROLES_TO_BLOCKED_LIST", func(d *schema.ResourceDiff) error { + // allowed := d.Get("external_oauth_allowed_roles_list").(*schema.Set) + // if allowed.Len() > 0 { + // return nil + // } + // set := d.Get("external_oauth_blocked_roles_list").(*schema.Set) + // set.Add("ACCOUNTADMIN") + // set.Add("SECURITYADMIN") + // return d.SetNew("external_oauth_blocked_roles_list", set) + // }), + ComputedIfAnyAttributeChanged(ShowOutputAttributeName, "enabled", "external_oauth_type", "comment"), + ComputedIfAnyAttributeChanged(DescribeOutputAttributeName, "enabled", "external_oauth_issuer", "external_oauth_jws_keys_url", "external_oauth_any_role_mode", + "external_oauth_rsa_public_key", "external_oauth_rsa_public_key_2", "external_oauth_blocked_roles_list", "external_oauth_allowed_roles_list", + "external_oauth_audience_list", "external_oauth_token_user_mapping_claim", "external_oauth_snowflake_user_mapping_attribute", "external_oauth_scope_delimiter", + "comment"), ), Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, + StateContext: ImportExternalOauthIntegration, }, } } +func ImportExternalOauthIntegration(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { + logging.DebugLogger.Printf("[DEBUG] Starting external oauth integration import") + client := meta.(*provider.Context).Client + id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) + + integration, err := client.SecurityIntegrations.ShowByID(ctx, id) + if err != nil { + return nil, err + } + + integrationProperties, err := client.SecurityIntegrations.Describe(ctx, id) + if err != nil { + return nil, err + } + + if err = d.Set("name", integration.Name); err != nil { + return nil, err + } + if err = d.Set("enabled", integration.Enabled); err != nil { + return nil, err + } + if oauthType, err := integration.SubType(); err == nil { + if err = d.Set("external_oauth_type", oauthType); err != nil { + return nil, err + } + } + if prop, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "EXTERNAL_OAUTH_ISSUER" }); err == nil { + if err = d.Set("external_oauth_issuer", prop.Value); err != nil { + return nil, err + } + } + if prop, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "EXTERNAL_OAUTH_JWS_KEYS_URL" + }); err == nil { + if err = d.Set("external_oauth_jws_keys_url", listValueToSlice(prop.Value, true)); err != nil { + return nil, err + } + } + if prop, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "EXTERNAL_OAUTH_ANY_ROLE_MODE" + }); err == nil { + if err = d.Set("external_oauth_any_role_mode", prop.Value); err != nil { + return nil, err + } + } + if prop, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "EXTERNAL_OAUTH_RSA_PUBLIC_KEY" + }); err == nil { + if err = d.Set("external_oauth_rsa_public_key", prop.Value); err != nil { + return nil, err + } + } + if prop, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "EXTERNAL_OAUTH_RSA_PUBLIC_KEY_2" + }); err == nil { + if err = d.Set("external_oauth_rsa_public_key_2", prop.Value); err != nil { + return nil, err + } + } + if prop, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "EXTERNAL_OAUTH_BLOCKED_ROLES_LIST" + }); err == nil { + if err = d.Set("external_oauth_blocked_roles_list", listValueToSlice(prop.Value, false)); err != nil { + return nil, err + } + } + if prop, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "EXTERNAL_OAUTH_ALLOWED_ROLES_LIST" + }); err == nil { + if err = d.Set("external_oauth_allowed_roles_list", listValueToSlice(prop.Value, false)); err != nil { + return nil, err + } + } + if prop, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "EXTERNAL_OAUTH_AUDIENCE_LIST" + }); err == nil { + if err = d.Set("external_oauth_audience_list", listValueToSlice(prop.Value, true)); err != nil { + return nil, err + } + } + if prop, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "EXTERNAL_OAUTH_TOKEN_USER_MAPPING_CLAIM" + }); err == nil { + if err = d.Set("external_oauth_token_user_mapping_claim", listValueToSlice(prop.Value, true)); err != nil { + return nil, err + } + } + if prop, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "EXTERNAL_OAUTH_SNOWFLAKE_USER_MAPPING_ATTRIBUTE" + }); err == nil { + if err = d.Set("external_oauth_snowflake_user_mapping_attribute", prop.Value); err != nil { + return nil, err + } + } + if prop, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "EXTERNAL_OAUTH_SCOPE_DELIMITER" + }); err == nil { + if err = d.Set("external_oauth_scope_delimiter", prop.Value); err != nil { + return nil, err + } + } + if prop, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "COMMENT" }); err == nil { + if err = d.Set("comment", prop.Value); err != nil { + return nil, err + } + } + return []*schema.ResourceData{d}, nil +} + func CreateContextExternalOauthIntegration(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*provider.Context).Client enabled := d.Get("enabled").(bool) externalOauthIssuer := d.Get("external_oauth_issuer").(string) externalOauthSnowflakeUserMappingAttributeRaw := d.Get("external_oauth_snowflake_user_mapping_attribute").(string) - externalOauthTokenUserMappingClaimRaw := expandStringList(d.Get("external_oauth_token_user_mapping_claims").(*schema.Set).List()) + externalOauthTokenUserMappingClaimRaw := expandStringList(d.Get("external_oauth_token_user_mapping_claim").(*schema.Set).List()) name := d.Get("name").(string) integrationTypeRaw := d.Get("external_oauth_type").(string) integrationType, err := sdk.ToExternalOauthSecurityIntegrationTypeOption(integrationTypeRaw) @@ -267,12 +387,12 @@ func CreateContextExternalOauthIntegration(ctx context.Context, d *schema.Resour return diag.FromErr(err) } - d.SetId(name) + d.SetId(helpers.EncodeSnowflakeID(id)) return ReadContextExternalOauthIntegration(false)(ctx, d, meta) } -func ReadContextExternalOauthIntegration(expectExternalChanges bool) schema.ReadContextFunc { +func ReadContextExternalOauthIntegration(withExternalChangesMarking bool) schema.ReadContextFunc { return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*provider.Context).Client id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) @@ -291,6 +411,10 @@ func ReadContextExternalOauthIntegration(expectExternalChanges bool) schema.Read } return diag.FromErr(err) } + integrationProperties, err := client.SecurityIntegrations.Describe(ctx, id) + if err != nil { + return diag.FromErr(err) + } if c := integration.Category; c != sdk.SecurityIntegrationCategory { return diag.FromErr(fmt.Errorf("expected %v to be a %s integration, got %v", id, sdk.SecurityIntegrationCategory, c)) @@ -302,10 +426,6 @@ func ReadContextExternalOauthIntegration(expectExternalChanges bool) schema.Read return diag.FromErr(err) } - if err := d.Set("created_on", integration.CreatedOn.String()); err != nil { - return diag.FromErr(err) - } - if err := d.Set("enabled", integration.Enabled); err != nil { return diag.FromErr(err) } @@ -316,122 +436,176 @@ func ReadContextExternalOauthIntegration(expectExternalChanges bool) schema.Read if err := d.Set("external_oauth_type", subType); err != nil { return diag.FromErr(err) } - properties, err := client.SecurityIntegrations.Describe(ctx, id) - if err != nil { - return diag.FromErr(err) + if withExternalChangesMarking { + externalOauthIssuer, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "EXTERNAL_OAUTH_ISSUER" }) + if err != nil { + return diag.FromErr(err) + } + externalOauthJwsKeysUrl, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "EXTERNAL_OAUTH_JWS_KEYS_URL" + }) + if err != nil { + return diag.FromErr(err) + } + externalOauthAnyRoleMode, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "EXTERNAL_OAUTH_ANY_ROLE_MODE" + }) + if err != nil { + return diag.FromErr(err) + } + externalOauthRsaPublicKey, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "EXTERNAL_OAUTH_RSA_PUBLIC_KEY" + }) + if err != nil { + return diag.FromErr(err) + } + externalOauthRsaPublicKey2, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "EXTERNAL_OAUTH_RSA_PUBLIC_KEY_2" + }) + if err != nil { + return diag.FromErr(err) + } + externalOauthBlockedRolesList, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "EXTERNAL_OAUTH_BLOCKED_ROLES_LIST" + }) + if err != nil { + return diag.FromErr(err) + } + externalOauthAllowedRolesList, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "EXTERNAL_OAUTH_ALLOWED_ROLES_LIST" + }) + if err != nil { + return diag.FromErr(err) + } + externalOauthAudienceList, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "EXTERNAL_OAUTH_AUDIENCE_LIST" + }) + if err != nil { + return diag.FromErr(err) + } + externalOauthTokenUserMappingClaim, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "EXTERNAL_OAUTH_TOKEN_USER_MAPPING_CLAIM" + }) + if err != nil { + return diag.FromErr(err) + } + externalOauthSnowflakeUserMappingAttribute, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "EXTERNAL_OAUTH_SNOWFLAKE_USER_MAPPING_ATTRIBUTE" + }) + if err != nil { + return diag.FromErr(err) + } + externalOauthScopeDelimiter, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "EXTERNAL_OAUTH_SCOPE_DELIMITER" + }) + if err != nil { + return diag.FromErr(err) + } + if err = handleExternalChangesToObjectInDescribe(d, + describeMapping{"external_oauth_issuer", "external_oauth_issuer", externalOauthIssuer.Value, externalOauthIssuer.Value, nil}, + describeMapping{"external_oauth_jws_keys_url", "external_oauth_jws_keys_url", externalOauthJwsKeysUrl.Value, externalOauthJwsKeysUrl.Value, nil}, + describeMapping{"external_oauth_any_role_mode", "external_oauth_any_role_mode", externalOauthAnyRoleMode.Value, externalOauthAnyRoleMode.Value, nil}, + describeMapping{"external_oauth_rsa_public_key", "external_oauth_rsa_public_key", externalOauthRsaPublicKey.Value, externalOauthRsaPublicKey.Value, nil}, + describeMapping{"external_oauth_rsa_public_key_2", "external_oauth_rsa_public_key_2", externalOauthRsaPublicKey2.Value, externalOauthRsaPublicKey2.Value, nil}, + describeMapping{"external_oauth_blocked_roles_list", "external_oauth_blocked_roles_list", externalOauthBlockedRolesList.Value, externalOauthBlockedRolesList.Value, nil}, + describeMapping{"external_oauth_allowed_roles_list", "external_oauth_allowed_roles_list", externalOauthAllowedRolesList.Value, externalOauthAllowedRolesList.Value, nil}, + describeMapping{"external_oauth_audience_list", "external_oauth_audience_list", externalOauthAudienceList.Value, externalOauthAudienceList.Value, nil}, + describeMapping{"external_oauth_token_user_mapping_claim", "external_oauth_token_user_mapping_claim", externalOauthTokenUserMappingClaim.Value, externalOauthTokenUserMappingClaim.Value, nil}, + describeMapping{"external_oauth_snowflake_user_mapping_attribute", "external_oauth_snowflake_user_mapping_attribute", externalOauthSnowflakeUserMappingAttribute.Value, externalOauthSnowflakeUserMappingAttribute.Value, nil}, + describeMapping{"external_oauth_scope_delimiter", "external_oauth_scope_delimiter", externalOauthScopeDelimiter.Value, externalOauthScopeDelimiter.Value, nil}, + ); err != nil { + return diag.FromErr(err) + } } - for _, property := range properties { - name := property.Name - value := property.Value - switch name { - case "COMMENT", "CREATED_ON", "ENABLED": - case "EXTERNAL_OAUTH_ALLOWED_ROLES_LIST": - var allowedRoles []string - if len(value) > 0 { - allowedRoles = strings.Split(value, ",") - } - if err := d.Set("external_oauth_allowed_roles_list", allowedRoles); err != nil { - return diag.FromErr(err) - } - case "EXTERNAL_OAUTH_ANY_ROLE_MODE": - if err := d.Set("external_oauth_any_role_mode", value); err != nil { + if !d.GetRawConfig().IsNull() { + if v := d.GetRawConfig().AsValueMap()["external_oauth_jws_keys_url"]; !v.IsNull() { + if err = d.Set("external_oauth_jws_keys_url", ctyValToSliceString(v.AsValueSlice())); err != nil { return diag.FromErr(err) } - case "EXTERNAL_OAUTH_AUDIENCE_LIST": - var audienceList []string - if len(value) > 0 { - audienceList = strings.Split(value, ",") - } - if err := d.Set("external_oauth_audience_list", audienceList); err != nil { - return diag.FromErr(err) - } - case "EXTERNAL_OAUTH_BLOCKED_ROLES_LIST": - var blockedRoles []string - if len(value) > 0 { - blockedRoles = strings.Split(value, ",") - } - if err := d.Set("external_oauth_blocked_roles_list", blockedRoles); err != nil { + } + if v := d.GetRawConfig().AsValueMap()["external_oauth_rsa_public_key"]; !v.IsNull() { + if err = d.Set("external_oauth_rsa_public_key", v.AsString()); err != nil { return diag.FromErr(err) } - case "EXTERNAL_OAUTH_ISSUER": - if err := d.Set("external_oauth_issuer", value); err != nil { + } + if v := d.GetRawConfig().AsValueMap()["external_oauth_rsa_public_key_2"]; !v.IsNull() { + if err = d.Set("external_oauth_rsa_public_key_2", v.AsString()); err != nil { return diag.FromErr(err) } - case "EXTERNAL_OAUTH_JWS_KEYS_URL": - var audienceList []string - if len(value) > 0 { - audienceList = strings.Split(value, ",") - } - if err := d.Set("external_oauth_jws_keys_url", audienceList); err != nil { + } + if v := d.GetRawConfig().AsValueMap()["external_oauth_blocked_roles_list"]; !v.IsNull() { + if err = d.Set("external_oauth_blocked_roles_list", ctyValToSliceString(v.AsValueSlice())); err != nil { return diag.FromErr(err) } - case "EXTERNAL_OAUTH_RSA_PUBLIC_KEY": - if err := d.Set("external_oauth_rsa_public_key", value); err != nil { + } + if v := d.GetRawConfig().AsValueMap()["external_oauth_allowed_roles_list"]; !v.IsNull() { + if err = d.Set("external_oauth_allowed_roles_list", ctyValToSliceString(v.AsValueSlice())); err != nil { return diag.FromErr(err) } - case "EXTERNAL_OAUTH_RSA_PUBLIC_KEY_2": - if err := d.Set("external_oauth_rsa_public_key", value); err != nil { + } + if v := d.GetRawConfig().AsValueMap()["external_oauth_audience_list"]; !v.IsNull() { + if err = d.Set("external_oauth_audience_list", ctyValToSliceString(v.AsValueSlice())); err != nil { return diag.FromErr(err) } - case "EXTERNAL_OAUTH_SCOPE_DELIMITER": - if err := d.Set("external_oauth_scope_delimiter", value); err != nil { + } + if v := d.GetRawConfig().AsValueMap()["external_oauth_any_role_mode"]; !v.IsNull() { + if err = d.Set("external_oauth_any_role_mode", v.AsString()); err != nil { return diag.FromErr(err) } - case "EXTERNAL_OAUTH_SCOPE_MAPPING_ATTRIBUTE": - if err := d.Set("external_oauth_scope_mapping_attribute", value); err != nil { + } + if v := d.GetRawConfig().AsValueMap()["external_oauth_scope_delimiter"]; !v.IsNull() { + if err = d.Set("external_oauth_scope_delimiter", v.AsString()); err != nil { return diag.FromErr(err) } - case "EXTERNAL_OAUTH_SNOWFLAKE_USER_MAPPING_ATTRIBUTE": - if err := d.Set("external_oauth_snowflake_user_mapping_attribute", value); err != nil { + } + if v := d.GetRawConfig().AsValueMap()["external_oauth_scope_mapping_attribute"]; !v.IsNull() { + if err = d.Set("external_oauth_scope_mapping_attribute", v.AsString()); err != nil { return diag.FromErr(err) } - case "EXTERNAL_OAUTH_TOKEN_USER_MAPPING_CLAIM": - value = strings.TrimLeft(value, "[") - value = strings.TrimRight(value, "]") - elems := strings.Split(value, ",") - for i := range elems { - elems[i] = strings.Trim(elems[i], " '") - } - if value == "" { - elems = nil - } - - if err := d.Set("external_oauth_token_user_mapping_claims", elems); err != nil { + } + if v := d.GetRawConfig().AsValueMap()["comment"]; !v.IsNull() { + if err = d.Set("comment", v.AsString()); err != nil { return diag.FromErr(err) } - default: - log.Printf("[WARN] unexpected property %v returned from Snowflake", name) } } - param := "EXTERNAL_OAUTH_ADD_PRIVILEGED_ROLES_TO_BLOCKED_LIST" - params, err := client.Parameters.ShowParameters(ctx, &sdk.ShowParametersOptions{ - Like: &sdk.Like{ - Pattern: sdk.Pointer(param), - }, - In: &sdk.ParametersIn{ - Account: sdk.Pointer(true), - }, - }) - if err != nil { + + if err = d.Set(ShowOutputAttributeName, []map[string]any{schemas.SecurityIntegrationToSchema(integration)}); err != nil { return diag.FromErr(err) } - var found *sdk.Parameter - for _, v := range params { - if v.Key == param { - found = v - break - } - } - if found == nil { - return diag.FromErr(fmt.Errorf("parameter %s not found", param)) - } - paramVal := helpers.StringToBool(found.Value) - if !paramVal { - return nil - } - if err := d.Set("external_oauth_add_privileged_roles_to_blocked_list", paramVal); err != nil { + + if err = d.Set(DescribeOutputAttributeName, []map[string]any{schemas.ExternalOauthSecurityIntegrationPropertiesToSchema(integrationProperties)}); err != nil { return diag.FromErr(err) } + // param := "EXTERNAL_OAUTH_ADD_PRIVILEGED_ROLES_TO_BLOCKED_LIST" + // params, err := client.Parameters.ShowParameters(ctx, &sdk.ShowParametersOptions{ + // Like: &sdk.Like{ + // Pattern: sdk.Pointer(param), + // }, + // In: &sdk.ParametersIn{ + // Account: sdk.Pointer(true), + // }, + // }) + // if err != nil { + // return diag.FromErr(err) + // } + // var found *sdk.Parameter + // for _, v := range params { + // if v.Key == param { + // found = v + // break + // } + // } + // if found == nil { + // return diag.FromErr(fmt.Errorf("parameter %s not found", param)) + // } + // paramVal := helpers.StringToBool(found.Value) + // if !paramVal { + // return nil + // } + // if err := d.Set("external_oauth_add_privileged_roles_to_blocked_list", paramVal); err != nil { + // return diag.FromErr(err) + // } return nil } } @@ -442,10 +616,12 @@ func UpdateContextExternalOauthIntegration(ctx context.Context, d *schema.Resour set, unset := sdk.NewExternalOauthIntegrationSetRequest(), sdk.NewExternalOauthIntegrationUnsetRequest() if d.HasChange("comment") { + // TODO(this pr) add to sdk // set.WithComment(sdk.StringAllowEmpty{Value: d.Get("comment").(string)}) } if d.HasChange("enabled") { + // this field is required set.WithEnabled(d.Get("enabled").(bool)) } @@ -467,6 +643,7 @@ func UpdateContextExternalOauthIntegration(ctx context.Context, d *schema.Resour } set.WithExternalOauthAnyRoleMode(value) } else { + // TODO(SNOW-1515781): use UNSET set.WithExternalOauthAnyRoleMode(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable) } } @@ -494,6 +671,7 @@ func UpdateContextExternalOauthIntegration(ctx context.Context, d *schema.Resour } if d.HasChange("external_oauth_issuer") { + // this field is required set.WithExternalOauthIssuer(d.Get("external_oauth_issuer").(string)) } @@ -510,37 +688,46 @@ func UpdateContextExternalOauthIntegration(ctx context.Context, d *schema.Resour if d.HasChange("external_oauth_rsa_public_key") { set.WithExternalOauthRsaPublicKey(d.Get("external_oauth_rsa_public_key").(string)) + if v, ok := d.GetOk("external_oauth_rsa_public_key"); ok { + set.WithExternalOauthRsaPublicKey2(v.(string)) + } + // else: force new } if d.HasChange("external_oauth_rsa_public_key_2") { - set.WithExternalOauthRsaPublicKey2(d.Get("external_oauth_rsa_public_key_2").(string)) + if v, ok := d.GetOk("external_oauth_rsa_public_key_2"); ok { + set.WithExternalOauthRsaPublicKey2(v.(string)) + } + // else: force new } if d.HasChange("external_oauth_scope_delimiter") { if v, ok := d.GetOk("external_oauth_scope_delimiter"); ok { set.WithExternalOauthScopeDelimiter(v.(string)) } else { + // TODO(SNOW-1515781): use UNSET set.WithExternalOauthScopeDelimiter(",") } } if d.HasChange("external_oauth_scope_mapping_attribute") { + // TODO(this pr) add to sdk // set.WithExternalOauthScopeMappingAttribute(d.Get("external_oauth_scope_mapping_attribute").(string)) } if d.HasChange("external_oauth_snowflake_user_mapping_attribute") { - v := d.Get("external_oauth_snowflake_user_mapping_attribute").(string) - if len(v) > 0 { - value, err := sdk.ToExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeOption(v) + if v, ok := d.GetOk("external_oauth_snowflake_user_mapping_attribute"); ok { + value, err := sdk.ToExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeOption(v.(string)) if err != nil { return diag.FromErr(err) } set.WithExternalOauthSnowflakeUserMappingAttribute(value) } + // else: force new } - if d.HasChange("external_oauth_token_user_mapping_claims") { - v := d.Get("external_oauth_token_user_mapping_claims").([]any) + if d.HasChange("external_oauth_token_user_mapping_claim") { + v := d.Get("external_oauth_token_user_mapping_claim").([]any) claims := make([]sdk.TokenUserMappingClaim, len(v)) for i := range v { claims[i] = sdk.TokenUserMappingClaim{ @@ -551,9 +738,8 @@ func UpdateContextExternalOauthIntegration(ctx context.Context, d *schema.Resour } if d.HasChange("external_oauth_type") { - v := d.Get("external_oauth_type").(string) - if len(v) > 0 { - value, err := sdk.ToExternalOauthSecurityIntegrationTypeOption(v) + if v, ok := d.GetOk("external_oauth_type"); ok { + value, err := sdk.ToExternalOauthSecurityIntegrationTypeOption(v.(string)) if err != nil { return diag.FromErr(err) } diff --git a/pkg/resources/external_oauth_integration_acceptance_test.go b/pkg/resources/external_oauth_integration_acceptance_test.go index c4f91e612f..ae223e7edc 100644 --- a/pkg/resources/external_oauth_integration_acceptance_test.go +++ b/pkg/resources/external_oauth_integration_acceptance_test.go @@ -32,7 +32,7 @@ func TestAcc_ExternalOauthIntegration_completeWithJwsKeysUrlAndAllowedRolesList( "external_oauth_scope_delimiter": config.StringVariable("."), "external_oauth_scope_mapping_attribute": config.StringVariable("foo"), "external_oauth_snowflake_user_mapping_attribute": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), - "external_oauth_token_user_mapping_claims": config.SetVariable(config.StringVariable("foo")), + "external_oauth_token_user_mapping_claim": config.SetVariable(config.StringVariable("foo")), "name": config.StringVariable(id.Name()), "type": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), } @@ -45,7 +45,7 @@ func TestAcc_ExternalOauthIntegration_completeWithJwsKeysUrlAndAllowedRolesList( }, Steps: []resource.TestStep{ { - ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrl"), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrlAndAllowedRolesList"), ConfigVariables: m(), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "comment", "foo"), @@ -61,19 +61,19 @@ func TestAcc_ExternalOauthIntegration_completeWithJwsKeysUrlAndAllowedRolesList( resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_scope_delimiter", "."), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_scope_mapping_attribute", "foo"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_snowflake_user_mapping_attribute", string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_token_user_mapping_claims.#", "1"), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_token_user_mapping_claims.0", "foo"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_token_user_mapping_claim.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_token_user_mapping_claim.0", "foo"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "name", id.Name()), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_type", string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), - resource.TestCheckResourceAttrSet("snowflake_external_oauth_integration.test", "created_on"), ), }, { - ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrl"), - ConfigVariables: m(), - ResourceName: "snowflake_external_oauth_integration.test", - ImportState: true, - ImportStateVerify: true, + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrlAndAllowedRolesList"), + ConfigVariables: m(), + ResourceName: "snowflake_external_oauth_integration.test", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"external_oauth_rsa_public_key", "external_oauth_rsa_public_key_2", "external_oauth_scope_mapping_attribute"}, }, }, }) @@ -96,7 +96,7 @@ func TestAcc_ExternalOauthIntegration_invalid(t *testing.T) { "external_oauth_scope_delimiter": config.StringVariable("foo"), "external_oauth_scope_mapping_attribute": config.StringVariable("foo"), "external_oauth_snowflake_user_mapping_attribute": config.StringVariable("foo"), - "external_oauth_token_user_mapping_claims": config.SetVariable(config.StringVariable("foo")), + "external_oauth_token_user_mapping_claim": config.SetVariable(config.StringVariable("foo")), "name": config.StringVariable("foo"), "type": config.StringVariable("foo"), } diff --git a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/basic/test.tf b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/basic/test.tf index aaa1f06f75..761a6133b1 100644 --- a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/basic/test.tf +++ b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/basic/test.tf @@ -1,19 +1,8 @@ resource "snowflake_external_oauth_integration" "test" { - comment = var.comment - enabled = var.enabled - external_oauth_add_privileged_roles_to_blocked_list = var.external_oauth_add_privileged_roles_to_blocked_list - external_oauth_allowed_roles_list = var.external_oauth_allowed_roles_list - external_oauth_any_role_mode = var.external_oauth_any_role_mode - external_oauth_audience_list = var.external_oauth_audience_list - external_oauth_blocked_roles_list = var.external_oauth_blocked_roles_list - external_oauth_issuer = var.external_oauth_issuer - external_oauth_jws_keys_url = var.external_oauth_jws_keys_url - external_oauth_rsa_public_key = var.external_oauth_rsa_public_key - external_oauth_rsa_public_key_2 = var.external_oauth_rsa_public_key_2 - external_oauth_scope_delimiter = var.external_oauth_scope_delimiter - external_oauth_scope_mapping_attribute = var.external_oauth_scope_mapping_attribute - external_oauth_snowflake_user_mapping_attribute = var.external_oauth_snowflake_user_mapping_attribute - external_oauth_token_user_mapping_claims = var.external_oauth_token_user_mapping_claims - name = var.name - type = var.type + name = var.name + type = var.type + enabled = var.enabled + external_oauth_issuer = var.external_oauth_issuer + external_oauth_scope_mapping_attribute = var.external_oauth_scope_mapping_attribute + external_oauth_snowflake_user_mapping_attribute = var.external_oauth_snowflake_user_mapping_attribute } diff --git a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/basic/variables.tf b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/basic/variables.tf index 4410ac597f..459b631194 100644 --- a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/basic/variables.tf +++ b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/basic/variables.tf @@ -1,52 +1,18 @@ - -variable "comment" { - type = string -} -variable "enabled" { - type = bool -} -variable "external_oauth_add_privileged_roles_to_blocked_list" { - type = bool -} -variable "external_oauth_allowed_roles_list" { - type = set -} -variable "external_oauth_any_role_mode" { - type = string +variable "name" { + type = string } -variable "external_oauth_audience_list" { - type = set +variable "type" { + type = string } -variable "external_oauth_blocked_roles_list" { - type = set +variable "enabled" { + type = bool } variable "external_oauth_issuer" { - type = string -} -variable "external_oauth_jws_keys_url" { - type = set -} -variable "external_oauth_rsa_public_key" { - type = string -} -variable "external_oauth_rsa_public_key_2" { - type = string -} -variable "external_oauth_scope_delimiter" { - type = string -} -variable "external_oauth_scope_mapping_attribute" { - type = string + type = string } variable "external_oauth_snowflake_user_mapping_attribute" { - type = string + type = string } -variable "external_oauth_token_user_mapping_claims" { - type = set -} -variable "name" { - type = string -} -variable "type" { - type = string +variable "external_oauth_token_user_mapping_claim" { + type = set(any) } diff --git a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrl/test.tf b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrlAndAllowedRolesList/test.tf similarity index 91% rename from pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrl/test.tf rename to pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrlAndAllowedRolesList/test.tf index eef7e07af3..9583250522 100644 --- a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrl/test.tf +++ b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrlAndAllowedRolesList/test.tf @@ -9,7 +9,7 @@ resource "snowflake_external_oauth_integration" "test" { external_oauth_scope_delimiter = var.external_oauth_scope_delimiter external_oauth_scope_mapping_attribute = var.external_oauth_scope_mapping_attribute external_oauth_snowflake_user_mapping_attribute = var.external_oauth_snowflake_user_mapping_attribute - external_oauth_token_user_mapping_claims = var.external_oauth_token_user_mapping_claims + external_oauth_token_user_mapping_claim = var.external_oauth_token_user_mapping_claim name = var.name external_oauth_type = var.type } diff --git a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrl/variables.tf b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrlAndAllowedRolesList/variables.tf similarity index 92% rename from pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrl/variables.tf rename to pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrlAndAllowedRolesList/variables.tf index 7d86487c9e..a329605ff7 100644 --- a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrl/variables.tf +++ b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrlAndAllowedRolesList/variables.tf @@ -29,7 +29,7 @@ variable "external_oauth_scope_mapping_attribute" { variable "external_oauth_snowflake_user_mapping_attribute" { type = string } -variable "external_oauth_token_user_mapping_claims" { +variable "external_oauth_token_user_mapping_claim" { type = set(string) } variable "name" { diff --git a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithRsaPublicKeysAndBlockedRolesList/test.tf b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithRsaPublicKeysAndBlockedRolesList/test.tf new file mode 100644 index 0000000000..b695200823 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithRsaPublicKeysAndBlockedRolesList/test.tf @@ -0,0 +1,16 @@ +resource "snowflake_external_oauth_integration" "test" { + comment = var.comment + enabled = var.enabled + external_oauth_blocked_roles_list = var.external_oauth_blocked_roles_list + external_oauth_any_role_mode = var.external_oauth_any_role_mode + external_oauth_audience_list = var.external_oauth_audience_list + external_oauth_issuer = var.external_oauth_issuer + external_oauth_rsa_public_key = var.external_oauth_rsa_public_key + external_oauth_rsa_public_key_2 = var.external_oauth_rsa_public_key_2 + external_oauth_scope_delimiter = var.external_oauth_scope_delimiter + external_oauth_scope_mapping_attribute = var.external_oauth_scope_mapping_attribute + external_oauth_snowflake_user_mapping_attribute = var.external_oauth_snowflake_user_mapping_attribute + external_oauth_token_user_mapping_claim = var.external_oauth_token_user_mapping_claim + name = var.name + external_oauth_type = var.type +} diff --git a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithRsaPublicKeysAndBlockedRolesList/variables.tf b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithRsaPublicKeysAndBlockedRolesList/variables.tf new file mode 100644 index 0000000000..8204a04da3 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithRsaPublicKeysAndBlockedRolesList/variables.tf @@ -0,0 +1,42 @@ +variable "comment" { + type = string +} +variable "enabled" { + type = bool +} +variable "external_oauth_blocked_roles_list" { + type = set(string) +} +variable "external_oauth_any_role_mode" { + type = string +} +variable "external_oauth_audience_list" { + type = set(string) +} +variable "external_oauth_issuer" { + type = string +} +variable "external_oauth_scope_delimiter" { + type = string +} +variable "external_oauth_scope_mapping_attribute" { + type = string +} +variable "external_oauth_snowflake_user_mapping_attribute" { + type = string +} +variable "external_oauth_token_user_mapping_claim" { + type = set(string) +} +variable "name" { + type = string +} +variable "type" { + type = string +} +variable "external_oauth_rsa_public_key" { + type = string +} +variable "external_oauth_rsa_public_key_2" { + type = string +} From be9b83068614ee73f8db36f28307512eff1d041d Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Wed, 3 Jul 2024 11:43:13 +0200 Subject: [PATCH 04/14] blocked roles list checkpoint --- pkg/resources/external_oauth_integration.go | 110 ++++---- ...ernal_oauth_integration_acceptance_test.go | 234 +++++++++++++++++- .../invalid/test.tf | 18 +- .../invalid/variables.tf | 51 +--- .../external_oauth_security_integration.go | 15 ++ pkg/sdk/security_integrations_def.go | 6 +- .../security_integrations_dto_builders_gen.go | 7 +- pkg/sdk/security_integrations_dto_gen.go | 3 +- pkg/sdk/security_integrations_gen.go | 33 +-- pkg/sdk/security_integrations_gen_test.go | 15 +- pkg/sdk/security_integrations_impl_gen.go | 49 +++- .../security_integrations_validations_gen.go | 4 +- ...urity_integrations_gen_integration_test.go | 2 +- 13 files changed, 379 insertions(+), 168 deletions(-) diff --git a/pkg/resources/external_oauth_integration.go b/pkg/resources/external_oauth_integration.go index 1c0e52fac6..dacc1e1536 100644 --- a/pkg/resources/external_oauth_integration.go +++ b/pkg/resources/external_oauth_integration.go @@ -93,10 +93,18 @@ var oauthExternalIntegrationSchema = map[string]*schema.Schema{ Optional: true, Description: "Specifies the list of roles that a client cannot set as the primary role. By default, this list includes the ACCOUNTADMIN, ORGADMIN, and SECURITYADMIN roles. To remove these privileged roles from the list, use the ALTER ACCOUNT command to set the EXTERNAL_OAUTH_ADD_PRIVILEGED_ROLES_TO_BLOCKED_LIST account parameter to FALSE.", DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - value := d.Get("external_oauth_add_privileged_roles_to_blocked_list").(bool) - if !value { - return old == new + params := d.Get(ParametersAttributeName).([]any) + var found *sdk.Parameter + for _, v := range params { + param := v.(sdk.Parameter) + if param.Key == "EXTERNAL_OAUTH_ADD_PRIVILEGED_ROLES_TO_BLOCKED_LIST" { + found = ¶m + } + } + if found == nil || !helpers.StringToBool(found.Value) { + return false } + return old == "ACCOUNTADMIN" || old == "SECURITYADMIN" }, ConflictsWith: []string{"external_oauth_allowed_roles_list"}, @@ -157,6 +165,14 @@ var oauthExternalIntegrationSchema = map[string]*schema.Schema{ Schema: schemas.DescribeExternalOauthSecurityIntegrationSchema, }, }, + ParametersAttributeName: { + Type: schema.TypeList, + Computed: true, + Description: "Paramteres related to this security integration.", + Elem: &schema.Resource{ + Schema: schemas.ShowExternalOauthParametersSchema, + }, + }, } func ExternalOauthIntegration() *schema.Resource { @@ -172,16 +188,6 @@ func ExternalOauthIntegration() *schema.Resource { ForceNewIfChangeToEmptyString("external_oauth_rsa_public_key_2"), ForceNewIfChangeToEmptyString("external_oauth_scope_mapping_attribute"), ForceNewIfChangeToEmptySet[any]("external_oauth_jws_keys_url"), - // ModifyStateIfParameterSet("external_oauth_blocked_roles_list", "EXTERNAL_OAUTH_ADD_PRIVILEGED_ROLES_TO_BLOCKED_LIST", func(d *schema.ResourceDiff) error { - // allowed := d.Get("external_oauth_allowed_roles_list").(*schema.Set) - // if allowed.Len() > 0 { - // return nil - // } - // set := d.Get("external_oauth_blocked_roles_list").(*schema.Set) - // set.Add("ACCOUNTADMIN") - // set.Add("SECURITYADMIN") - // return d.SetNew("external_oauth_blocked_roles_list", set) - // }), ComputedIfAnyAttributeChanged(ShowOutputAttributeName, "enabled", "external_oauth_type", "comment"), ComputedIfAnyAttributeChanged(DescribeOutputAttributeName, "enabled", "external_oauth_issuer", "external_oauth_jws_keys_url", "external_oauth_any_role_mode", "external_oauth_rsa_public_key", "external_oauth_rsa_public_key_2", "external_oauth_blocked_roles_list", "external_oauth_allowed_roles_list", @@ -503,20 +509,21 @@ func ReadContextExternalOauthIntegration(withExternalChangesMarking bool) schema } if err = handleExternalChangesToObjectInDescribe(d, describeMapping{"external_oauth_issuer", "external_oauth_issuer", externalOauthIssuer.Value, externalOauthIssuer.Value, nil}, - describeMapping{"external_oauth_jws_keys_url", "external_oauth_jws_keys_url", externalOauthJwsKeysUrl.Value, externalOauthJwsKeysUrl.Value, nil}, + describeMapping{"external_oauth_jws_keys_url", "external_oauth_jws_keys_url", externalOauthJwsKeysUrl.Value, listValueToSlice(externalOauthJwsKeysUrl.Value, true), nil}, describeMapping{"external_oauth_any_role_mode", "external_oauth_any_role_mode", externalOauthAnyRoleMode.Value, externalOauthAnyRoleMode.Value, nil}, describeMapping{"external_oauth_rsa_public_key", "external_oauth_rsa_public_key", externalOauthRsaPublicKey.Value, externalOauthRsaPublicKey.Value, nil}, describeMapping{"external_oauth_rsa_public_key_2", "external_oauth_rsa_public_key_2", externalOauthRsaPublicKey2.Value, externalOauthRsaPublicKey2.Value, nil}, - describeMapping{"external_oauth_blocked_roles_list", "external_oauth_blocked_roles_list", externalOauthBlockedRolesList.Value, externalOauthBlockedRolesList.Value, nil}, - describeMapping{"external_oauth_allowed_roles_list", "external_oauth_allowed_roles_list", externalOauthAllowedRolesList.Value, externalOauthAllowedRolesList.Value, nil}, - describeMapping{"external_oauth_audience_list", "external_oauth_audience_list", externalOauthAudienceList.Value, externalOauthAudienceList.Value, nil}, - describeMapping{"external_oauth_token_user_mapping_claim", "external_oauth_token_user_mapping_claim", externalOauthTokenUserMappingClaim.Value, externalOauthTokenUserMappingClaim.Value, nil}, + describeMapping{"external_oauth_blocked_roles_list", "external_oauth_blocked_roles_list", externalOauthBlockedRolesList.Value, listValueToSlice(externalOauthBlockedRolesList.Value, false), nil}, + describeMapping{"external_oauth_allowed_roles_list", "external_oauth_allowed_roles_list", externalOauthAllowedRolesList.Value, listValueToSlice(externalOauthAllowedRolesList.Value, false), nil}, + describeMapping{"external_oauth_audience_list", "external_oauth_audience_list", externalOauthAudienceList.Value, listValueToSlice(externalOauthAudienceList.Value, false), nil}, + describeMapping{"external_oauth_token_user_mapping_claim", "external_oauth_token_user_mapping_claim", externalOauthTokenUserMappingClaim.Value, listValueToSlice(externalOauthTokenUserMappingClaim.Value, true), nil}, describeMapping{"external_oauth_snowflake_user_mapping_attribute", "external_oauth_snowflake_user_mapping_attribute", externalOauthSnowflakeUserMappingAttribute.Value, externalOauthSnowflakeUserMappingAttribute.Value, nil}, describeMapping{"external_oauth_scope_delimiter", "external_oauth_scope_delimiter", externalOauthScopeDelimiter.Value, externalOauthScopeDelimiter.Value, nil}, ); err != nil { return diag.FromErr(err) } } + if !d.GetRawConfig().IsNull() { if v := d.GetRawConfig().AsValueMap()["external_oauth_jws_keys_url"]; !v.IsNull() { if err = d.Set("external_oauth_jws_keys_url", ctyValToSliceString(v.AsValueSlice())); err != nil { @@ -577,35 +584,38 @@ func ReadContextExternalOauthIntegration(withExternalChangesMarking bool) schema if err = d.Set(DescribeOutputAttributeName, []map[string]any{schemas.ExternalOauthSecurityIntegrationPropertiesToSchema(integrationProperties)}); err != nil { return diag.FromErr(err) } - // param := "EXTERNAL_OAUTH_ADD_PRIVILEGED_ROLES_TO_BLOCKED_LIST" - // params, err := client.Parameters.ShowParameters(ctx, &sdk.ShowParametersOptions{ - // Like: &sdk.Like{ - // Pattern: sdk.Pointer(param), - // }, - // In: &sdk.ParametersIn{ - // Account: sdk.Pointer(true), - // }, - // }) - // if err != nil { - // return diag.FromErr(err) - // } - // var found *sdk.Parameter - // for _, v := range params { - // if v.Key == param { - // found = v - // break - // } - // } - // if found == nil { - // return diag.FromErr(fmt.Errorf("parameter %s not found", param)) - // } - // paramVal := helpers.StringToBool(found.Value) - // if !paramVal { - // return nil - // } - // if err := d.Set("external_oauth_add_privileged_roles_to_blocked_list", paramVal); err != nil { - // return diag.FromErr(err) - // } + + params, err := client.Parameters.ShowParameters(ctx, &sdk.ShowParametersOptions{ + Like: &sdk.Like{ + Pattern: sdk.Pointer("%%EXTERNAL_OAUTH%%"), + }, + In: &sdk.ParametersIn{ + Account: sdk.Pointer(true), + }, + }) + if err != nil { + return diag.FromErr(err) + } + if err = d.Set(ParametersAttributeName, []map[string]any{schemas.ExternalOauthParametersToSchema(params)}); err != nil { + return diag.FromErr(err) + } + // handle blocked_roles_list + found, err := collections.FindOne(params, func(p *sdk.Parameter) bool { return p.Key == "EXTERNAL_OAUTH_ADD_PRIVILEGED_ROLES_TO_BLOCKED_LIST" }) + if err == nil && (*found).Value == "true" { + externalOauthBlockedRolesList, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "EXTERNAL_OAUTH_BLOCKED_ROLES_LIST" + }) + x := d.Get("external_oauth_blocked_roles_list") + fmt.Println(x) + if err != nil { + return diag.FromErr(err) + } + if err := d.Set("external_oauth_blocked_roles_list", listValueToSlice(externalOauthBlockedRolesList.Value, false)); err != nil { + return diag.FromErr(err) + } + x = d.Get("external_oauth_blocked_roles_list") + fmt.Println(x) + } return nil } } @@ -616,8 +626,7 @@ func UpdateContextExternalOauthIntegration(ctx context.Context, d *schema.Resour set, unset := sdk.NewExternalOauthIntegrationSetRequest(), sdk.NewExternalOauthIntegrationUnsetRequest() if d.HasChange("comment") { - // TODO(this pr) add to sdk - // set.WithComment(sdk.StringAllowEmpty{Value: d.Get("comment").(string)}) + set.WithComment(sdk.StringAllowEmpty{Value: d.Get("comment").(string)}) } if d.HasChange("enabled") { @@ -711,8 +720,7 @@ func UpdateContextExternalOauthIntegration(ctx context.Context, d *schema.Resour } if d.HasChange("external_oauth_scope_mapping_attribute") { - // TODO(this pr) add to sdk - // set.WithExternalOauthScopeMappingAttribute(d.Get("external_oauth_scope_mapping_attribute").(string)) + set.WithExternalOauthScopeMappingAttribute(d.Get("external_oauth_scope_mapping_attribute").(string)) } if d.HasChange("external_oauth_snowflake_user_mapping_attribute") { diff --git a/pkg/resources/external_oauth_integration_acceptance_test.go b/pkg/resources/external_oauth_integration_acceptance_test.go index ae223e7edc..384fa8a69f 100644 --- a/pkg/resources/external_oauth_integration_acceptance_test.go +++ b/pkg/resources/external_oauth_integration_acceptance_test.go @@ -1,6 +1,9 @@ package resources_test import ( + "fmt" + "sort" + "strings" "testing" acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" @@ -13,6 +16,9 @@ import ( "github.com/hashicorp/terraform-plugin-testing/tfversion" ) +func TestAcc_ExternalOauthIntegration_basic(t *testing.T) { +} + func TestAcc_ExternalOauthIntegration_completeWithJwsKeysUrlAndAllowedRolesList(t *testing.T) { id := acc.TestClient().Ids.RandomAccountObjectIdentifier() role1, role1Cleanup := acc.TestClient().Role.CreateRole(t) @@ -79,6 +85,190 @@ func TestAcc_ExternalOauthIntegration_completeWithJwsKeysUrlAndAllowedRolesList( }) } +func TestAcc_ExternalOauthIntegration_completeWithRsaPublicKeysAndBlockedRolesList_paramSet(t *testing.T) { + id := acc.TestClient().Ids.RandomAccountObjectIdentifier() + role1, role1Cleanup := acc.TestClient().Role.CreateRole(t) + t.Cleanup(role1Cleanup) + expectedRoles := []string{"ACCOUNTADMIN", "SECURITYADMIN", role1.Name} + sort.Strings(expectedRoles) + issuer := random.String() + rsaKey := random.GenerateRSAPublicKey(t) + paramCleanup := acc.TestClient().Parameter.UpdateAccountParameterTemporarily(t, sdk.AccountParameterExternalOAuthAddPrivilegedRolesToBlockedList, "true") + t.Cleanup(paramCleanup) + m := func() map[string]config.Variable { + return map[string]config.Variable{ + "comment": config.StringVariable("foo"), + "enabled": config.BoolVariable(true), + "external_oauth_add_privileged_roles_to_blocked_list": config.BoolVariable(true), + "external_oauth_blocked_roles_list": config.SetVariable(config.StringVariable(role1.ID().Name())), + "external_oauth_any_role_mode": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), + "external_oauth_audience_list": config.SetVariable(config.StringVariable("foo")), + "external_oauth_issuer": config.StringVariable(issuer), + "external_oauth_rsa_public_key": config.StringVariable(rsaKey), + "external_oauth_rsa_public_key_2": config.StringVariable(rsaKey), + "external_oauth_scope_delimiter": config.StringVariable("."), + "external_oauth_scope_mapping_attribute": config.StringVariable("foo"), + "external_oauth_snowflake_user_mapping_attribute": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), + "external_oauth_token_user_mapping_claim": config.SetVariable(config.StringVariable("foo")), + "name": config.StringVariable(id.Name()), + "type": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), + } + } + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/completeWithRsaPublicKeysAndBlockedRolesList"), + ConfigVariables: m(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "comment", "foo"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "enabled", "true"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_blocked_roles_list.#", "3"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_blocked_roles_list.0", role1.ID().Name()), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_any_role_mode", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_audience_list.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_audience_list.0", "foo"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_issuer", issuer), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_rsa_public_key", rsaKey), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_scope_delimiter", "."), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_scope_mapping_attribute", "foo"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_snowflake_user_mapping_attribute", string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_token_user_mapping_claim.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_token_user_mapping_claim.0", "foo"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "name", id.Name()), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_type", string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), + + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.name", id.Name()), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.integration_type", "EXTERNAL_OAUTH - CUSTOM"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.category", "SECURITY"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.enabled", "true"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.comment", "foo"), + resource.TestCheckResourceAttrSet("snowflake_external_oauth_integration.test", "show_output.0.created_on"), + + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.enabled.0.value", "true"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_issuer.0.value", issuer), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_jws_keys_url.0.value", ""), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_any_role_mode.0.value", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_rsa_public_key.0.value", rsaKey), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_rsa_public_key_2.0.value", rsaKey), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_blocked_roles_list.0.value", strings.Join(expectedRoles, ",")), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_allowed_roles_list.0.value", ""), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_audience_list.0.value", "foo"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_token_user_mapping_claim.0.value", "['foo']"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_snowflake_user_mapping_attribute.0.value", string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_scope_delimiter.0.value", "."), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.comment.0.value", "foo"), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/completeWithRsaPublicKeysAndBlockedRolesList"), + ConfigVariables: m(), + ResourceName: "snowflake_external_oauth_integration.test", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"external_oauth_rsa_public_key", "external_oauth_rsa_public_key_2", "external_oauth_scope_mapping_attribute"}, + }, + }, + }) +} + +func TestAcc_ExternalOauthIntegration_completeWithRsaPublicKeysAndBlockedRolesList_paramUnset(t *testing.T) { + id := acc.TestClient().Ids.RandomAccountObjectIdentifier() + role1, role1Cleanup := acc.TestClient().Role.CreateRole(t) + t.Cleanup(role1Cleanup) + issuer := random.String() + rsaKey := random.GenerateRSAPublicKey(t) + paramCleanup := acc.TestClient().Parameter.UpdateAccountParameterTemporarily(t, sdk.AccountParameterExternalOAuthAddPrivilegedRolesToBlockedList, "false") + t.Cleanup(paramCleanup) + m := func() map[string]config.Variable { + return map[string]config.Variable{ + "comment": config.StringVariable("foo"), + "enabled": config.BoolVariable(true), + "external_oauth_add_privileged_roles_to_blocked_list": config.BoolVariable(true), + "external_oauth_blocked_roles_list": config.SetVariable(config.StringVariable(role1.ID().Name())), + "external_oauth_any_role_mode": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), + "external_oauth_audience_list": config.SetVariable(config.StringVariable("foo")), + "external_oauth_issuer": config.StringVariable(issuer), + "external_oauth_rsa_public_key": config.StringVariable(rsaKey), + "external_oauth_rsa_public_key_2": config.StringVariable(rsaKey), + "external_oauth_scope_delimiter": config.StringVariable("."), + "external_oauth_scope_mapping_attribute": config.StringVariable("foo"), + "external_oauth_snowflake_user_mapping_attribute": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), + "external_oauth_token_user_mapping_claim": config.SetVariable(config.StringVariable("foo")), + "name": config.StringVariable(id.Name()), + "type": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), + } + } + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/completeWithRsaPublicKeysAndBlockedRolesList"), + ConfigVariables: m(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "comment", "foo"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "enabled", "true"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_blocked_roles_list.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_blocked_roles_list.0", role1.ID().Name()), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_any_role_mode", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_audience_list.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_audience_list.0", "foo"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_issuer", issuer), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_rsa_public_key", rsaKey), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_scope_delimiter", "."), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_scope_mapping_attribute", "foo"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_snowflake_user_mapping_attribute", string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_token_user_mapping_claim.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_token_user_mapping_claim.0", "foo"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "name", id.Name()), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_type", string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), + + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.name", id.Name()), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.integration_type", "EXTERNAL_OAUTH - CUSTOM"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.category", "SECURITY"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.enabled", "true"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.comment", "foo"), + resource.TestCheckResourceAttrSet("snowflake_external_oauth_integration.test", "show_output.0.created_on"), + + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.enabled.0.value", "true"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_issuer.0.value", issuer), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_jws_keys_url.0.value", ""), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_any_role_mode.0.value", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_rsa_public_key.0.value", rsaKey), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_rsa_public_key_2.0.value", rsaKey), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_blocked_roles_list.0.value", role1.Name), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_allowed_roles_list.0.value", ""), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_audience_list.0.value", "foo"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_token_user_mapping_claim.0.value", "['foo']"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_snowflake_user_mapping_attribute.0.value", string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_scope_delimiter.0.value", "."), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.comment.0.value", "foo"), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/completeWithRsaPublicKeysAndBlockedRolesList"), + ConfigVariables: m(), + ResourceName: "snowflake_external_oauth_integration.test", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"external_oauth_rsa_public_key", "external_oauth_rsa_public_key_2", "external_oauth_scope_mapping_attribute"}, + }, + }, + }) +} + func TestAcc_ExternalOauthIntegration_invalid(t *testing.T) { m := func() map[string]config.Variable { return map[string]config.Variable{ @@ -86,7 +276,7 @@ func TestAcc_ExternalOauthIntegration_invalid(t *testing.T) { "enabled": config.BoolVariable(true), "external_oauth_add_privileged_roles_to_blocked_list": config.BoolVariable(true), "external_oauth_allowed_roles_list": config.SetVariable(config.StringVariable("foo")), - "external_oauth_any_role_mode": config.StringVariable("foo"), + "external_oauth_any_role_mode": config.StringVariable("invalid"), "external_oauth_audience_list": config.SetVariable(config.StringVariable("foo")), "external_oauth_blocked_roles_list": config.SetVariable(config.StringVariable("foo")), "external_oauth_issuer": config.StringVariable("foo"), @@ -95,10 +285,10 @@ func TestAcc_ExternalOauthIntegration_invalid(t *testing.T) { "external_oauth_rsa_public_key_2": config.StringVariable("foo"), "external_oauth_scope_delimiter": config.StringVariable("foo"), "external_oauth_scope_mapping_attribute": config.StringVariable("foo"), - "external_oauth_snowflake_user_mapping_attribute": config.StringVariable("foo"), + "external_oauth_snowflake_user_mapping_attribute": config.StringVariable("invalid"), "external_oauth_token_user_mapping_claim": config.SetVariable(config.StringVariable("foo")), "name": config.StringVariable("foo"), - "type": config.StringVariable("foo"), + "type": config.StringVariable("invalid"), } } resource.Test(t, resource.TestCase{ @@ -109,12 +299,44 @@ func TestAcc_ExternalOauthIntegration_invalid(t *testing.T) { }, Steps: []resource.TestStep{ { - ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/complete"), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrlAndAllowedRolesList"), ConfigVariables: m(), - ExpectError: helpers.MatchAllStringsInOrderNonOverlapping([]string{ - // TODO: Implement + ExpectError: helpers.MatchAllStringsInOrderNonOverlapping([]string{ + fmt.Sprintf("expected external_oauth_any_role_mode to be one of %q, got invalid", sdk.AsStringList(sdk.AllExternalOauthSecurityIntegrationAnyRoleModes)), + fmt.Sprintf("expected external_oauth_snowflake_user_mapping_attribute to be one of %q, got invalid", sdk.AsStringList(sdk.AllExternalOauthSecurityIntegrationSnowflakeUserMappingAttributes)), + fmt.Sprintf("expected external_oauth_type to be one of %q, got invalid", sdk.AsStringList(sdk.AllExternalOauthSecurityIntegrationTypes)), }), }, }, }) } + +func TestAcc_ExternalOauthIntegration_InvalidIncomplete(t *testing.T) { + id := acc.TestClient().Ids.RandomAccountObjectIdentifier() + m := func() map[string]config.Variable { + return map[string]config.Variable{ + "name": config.StringVariable(id.Name()), + } + } + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + ErrorCheck: helpers.AssertErrorContainsPartsFunc(t, []string{ + // Some strings are trimmed because of inconsistent '\n' placement from tf error messages. + `The argument "external_oauth_type" is required, but no definition was found.`, + `The argument "external_oauth_snowflake_user_mapping_attribute" is required,`, + `The argument "enabled" is required, but no definition was found.`, + `The argument "external_oauth_issuer" is required,`, + `The argument "external_oauth_token_user_mapping_claim" is required,`, + }), + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/invalid"), + ConfigVariables: m(), + }, + }, + }) +} diff --git a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/invalid/test.tf b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/invalid/test.tf index aaa1f06f75..8cc64d16dd 100644 --- a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/invalid/test.tf +++ b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/invalid/test.tf @@ -1,19 +1,3 @@ resource "snowflake_external_oauth_integration" "test" { - comment = var.comment - enabled = var.enabled - external_oauth_add_privileged_roles_to_blocked_list = var.external_oauth_add_privileged_roles_to_blocked_list - external_oauth_allowed_roles_list = var.external_oauth_allowed_roles_list - external_oauth_any_role_mode = var.external_oauth_any_role_mode - external_oauth_audience_list = var.external_oauth_audience_list - external_oauth_blocked_roles_list = var.external_oauth_blocked_roles_list - external_oauth_issuer = var.external_oauth_issuer - external_oauth_jws_keys_url = var.external_oauth_jws_keys_url - external_oauth_rsa_public_key = var.external_oauth_rsa_public_key - external_oauth_rsa_public_key_2 = var.external_oauth_rsa_public_key_2 - external_oauth_scope_delimiter = var.external_oauth_scope_delimiter - external_oauth_scope_mapping_attribute = var.external_oauth_scope_mapping_attribute - external_oauth_snowflake_user_mapping_attribute = var.external_oauth_snowflake_user_mapping_attribute - external_oauth_token_user_mapping_claims = var.external_oauth_token_user_mapping_claims - name = var.name - type = var.type + name = var.name } diff --git a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/invalid/variables.tf b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/invalid/variables.tf index 4410ac597f..77e5cc9698 100644 --- a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/invalid/variables.tf +++ b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/invalid/variables.tf @@ -1,52 +1,3 @@ - -variable "comment" { - type = string -} -variable "enabled" { - type = bool -} -variable "external_oauth_add_privileged_roles_to_blocked_list" { - type = bool -} -variable "external_oauth_allowed_roles_list" { - type = set -} -variable "external_oauth_any_role_mode" { - type = string -} -variable "external_oauth_audience_list" { - type = set -} -variable "external_oauth_blocked_roles_list" { - type = set -} -variable "external_oauth_issuer" { - type = string -} -variable "external_oauth_jws_keys_url" { - type = set -} -variable "external_oauth_rsa_public_key" { - type = string -} -variable "external_oauth_rsa_public_key_2" { - type = string -} -variable "external_oauth_scope_delimiter" { - type = string -} -variable "external_oauth_scope_mapping_attribute" { - type = string -} -variable "external_oauth_snowflake_user_mapping_attribute" { - type = string -} -variable "external_oauth_token_user_mapping_claims" { - type = set -} variable "name" { - type = string -} -variable "type" { - type = string + type = string } diff --git a/pkg/schemas/external_oauth_security_integration.go b/pkg/schemas/external_oauth_security_integration.go index 634f2a9040..544d057690 100644 --- a/pkg/schemas/external_oauth_security_integration.go +++ b/pkg/schemas/external_oauth_security_integration.go @@ -1,6 +1,7 @@ package schemas import ( + "slices" "strings" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" @@ -51,3 +52,17 @@ func ExternalOauthSecurityIntegrationPropertiesToSchema(securityIntegrationPrope } var _ = ExternalOauthSecurityIntegrationPropertiesToSchema + +var ShowExternalOauthParametersSchema = map[string]*schema.Schema{ + strings.ToLower(string(sdk.AccountParameterExternalOAuthAddPrivilegedRolesToBlockedList)): ParameterListSchema, +} + +func ExternalOauthParametersToSchema(parameters []*sdk.Parameter) map[string]any { + schemaMap := make(map[string]any) + for _, param := range parameters { + if slices.Contains([]sdk.AccountParameter{sdk.AccountParameterExternalOAuthAddPrivilegedRolesToBlockedList}, sdk.AccountParameter(param.Key)) { + schemaMap[strings.ToLower(param.Key)] = []map[string]any{ParameterToSchema(param)} + } + } + return schemaMap +} diff --git a/pkg/sdk/security_integrations_def.go b/pkg/sdk/security_integrations_def.go index 8453a8df51..19409ed1a6 100644 --- a/pkg/sdk/security_integrations_def.go +++ b/pkg/sdk/security_integrations_def.go @@ -318,13 +318,15 @@ var externalOauthIntegrationSetDef = g.NewQueryStruct("ExternalOauthIntegrationS g.ParameterOptions(), ). OptionalTextAssignment("EXTERNAL_OAUTH_SCOPE_DELIMITER", g.ParameterOptions().SingleQuotes()). - OptionalComment(). + OptionalTextAssignment("EXTERNAL_OAUTH_SCOPE_MAPPING_ATTRIBUTE", g.ParameterOptions().SingleQuotes()). + // TODO(SNOW-1461780): use COMMENT in unset and here use OptionalComment + OptionalAssignment("COMMENT", "StringAllowEmpty", g.ParameterOptions()). WithValidation(g.ConflictingFields, "ExternalOauthBlockedRolesList", "ExternalOauthAllowedRolesList"). WithValidation(g.ConflictingFields, "ExternalOauthJwsKeysUrl", "ExternalOauthRsaPublicKey"). WithValidation(g.ConflictingFields, "ExternalOauthJwsKeysUrl", "ExternalOauthRsaPublicKey2"). WithValidation(g.AtLeastOneValueSet, "Enabled", "ExternalOauthType", "ExternalOauthIssuer", "ExternalOauthTokenUserMappingClaim", "ExternalOauthSnowflakeUserMappingAttribute", "ExternalOauthJwsKeysUrl", "ExternalOauthBlockedRolesList", "ExternalOauthAllowedRolesList", "ExternalOauthRsaPublicKey", "ExternalOauthRsaPublicKey2", - "ExternalOauthAudienceList", "ExternalOauthAnyRoleMode", "ExternalOauthScopeDelimiter", "Comment") + "ExternalOauthAudienceList", "ExternalOauthAnyRoleMode", "ExternalOauthScopeDelimiter", "ExternalOauthScopeMappingAttribute", "Comment") var externalOauthIntegrationUnsetDef = g.NewQueryStruct("ExternalOauthIntegrationUnset"). OptionalSQL("ENABLED"). diff --git a/pkg/sdk/security_integrations_dto_builders_gen.go b/pkg/sdk/security_integrations_dto_builders_gen.go index 2f27153cbb..dd59cf0913 100644 --- a/pkg/sdk/security_integrations_dto_builders_gen.go +++ b/pkg/sdk/security_integrations_dto_builders_gen.go @@ -966,7 +966,12 @@ func (s *ExternalOauthIntegrationSetRequest) WithExternalOauthScopeDelimiter(Ext return s } -func (s *ExternalOauthIntegrationSetRequest) WithComment(Comment string) *ExternalOauthIntegrationSetRequest { +func (s *ExternalOauthIntegrationSetRequest) WithExternalOauthScopeMappingAttribute(ExternalOauthScopeMappingAttribute string) *ExternalOauthIntegrationSetRequest { + s.ExternalOauthScopeMappingAttribute = &ExternalOauthScopeMappingAttribute + return s +} + +func (s *ExternalOauthIntegrationSetRequest) WithComment(Comment StringAllowEmpty) *ExternalOauthIntegrationSetRequest { s.Comment = &Comment return s } diff --git a/pkg/sdk/security_integrations_dto_gen.go b/pkg/sdk/security_integrations_dto_gen.go index d788094ce5..1f1d51d54b 100644 --- a/pkg/sdk/security_integrations_dto_gen.go +++ b/pkg/sdk/security_integrations_dto_gen.go @@ -299,7 +299,8 @@ type ExternalOauthIntegrationSetRequest struct { ExternalOauthAudienceList *AudienceListRequest ExternalOauthAnyRoleMode *ExternalOauthSecurityIntegrationAnyRoleModeOption ExternalOauthScopeDelimiter *string - Comment *string + ExternalOauthScopeMappingAttribute *string + Comment *StringAllowEmpty } type ExternalOauthIntegrationUnsetRequest struct { diff --git a/pkg/sdk/security_integrations_gen.go b/pkg/sdk/security_integrations_gen.go index c02f1979eb..6c106ee2e9 100644 --- a/pkg/sdk/security_integrations_gen.go +++ b/pkg/sdk/security_integrations_gen.go @@ -51,7 +51,6 @@ type CreateApiAuthenticationWithClientCredentialsFlowSecurityIntegrationOptions OauthAllowedScopes []AllowedScope `ddl:"parameter,parentheses" sql:"OAUTH_ALLOWED_SCOPES"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } - type AllowedScope struct { Scope string `ddl:"keyword,single_quotes"` } @@ -123,27 +122,21 @@ type CreateExternalOauthSecurityIntegrationOptions struct { ExternalOauthScopeMappingAttribute *string `ddl:"parameter,single_quotes" sql:"EXTERNAL_OAUTH_SCOPE_MAPPING_ATTRIBUTE"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } - type AllowedRolesList struct { AllowedRolesList []AccountObjectIdentifier `ddl:"list,must_parentheses"` } - type BlockedRolesList struct { BlockedRolesList []AccountObjectIdentifier `ddl:"list,must_parentheses"` } - type JwsKeysUrl struct { JwsKeyUrl string `ddl:"keyword,single_quotes"` } - type AudienceList struct { AudienceList []AudienceListItem `ddl:"list,must_parentheses"` } - type AudienceListItem struct { Item string `ddl:"keyword,single_quotes"` } - type TokenUserMappingClaim struct { Claim string `ddl:"keyword,single_quotes"` } @@ -165,7 +158,6 @@ type CreateOauthForPartnerApplicationsSecurityIntegrationOptions struct { BlockedRolesList *BlockedRolesList `ddl:"parameter,parentheses" sql:"BLOCKED_ROLES_LIST"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } - type PreAuthorizedRolesList struct { PreAuthorizedRolesList []AccountObjectIdentifier `ddl:"list,must_parentheses"` } @@ -221,11 +213,9 @@ type CreateSaml2SecurityIntegrationOptions struct { Saml2SnowflakeAcsUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_SNOWFLAKE_ACS_URL"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } - type UserDomain struct { Domain string `ddl:"keyword,single_quotes"` } - type EmailPattern struct { Pattern string `ddl:"keyword,single_quotes"` } @@ -257,7 +247,6 @@ type AlterApiAuthenticationWithClientCredentialsFlowSecurityIntegrationOptions s Set *ApiAuthenticationWithClientCredentialsFlowIntegrationSet `ddl:"list,no_parentheses" sql:"SET"` Unset *ApiAuthenticationWithClientCredentialsFlowIntegrationUnset `ddl:"list,no_parentheses" sql:"UNSET"` } - type ApiAuthenticationWithClientCredentialsFlowIntegrationSet struct { Enabled *bool `ddl:"parameter" sql:"ENABLED"` OauthTokenEndpoint *string `ddl:"parameter,single_quotes" sql:"OAUTH_TOKEN_ENDPOINT"` @@ -270,7 +259,6 @@ type ApiAuthenticationWithClientCredentialsFlowIntegrationSet struct { OauthAllowedScopes []AllowedScope `ddl:"parameter,parentheses" sql:"OAUTH_ALLOWED_SCOPES"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } - type ApiAuthenticationWithClientCredentialsFlowIntegrationUnset struct { Enabled *bool `ddl:"keyword" sql:"ENABLED"` Comment *bool `ddl:"keyword" sql:"COMMENT"` @@ -287,7 +275,6 @@ type AlterApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationOpti Set *ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSet `ddl:"list,no_parentheses" sql:"SET"` Unset *ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationUnset `ddl:"list,no_parentheses" sql:"UNSET"` } - type ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSet struct { Enabled *bool `ddl:"parameter" sql:"ENABLED"` OauthAuthorizationEndpoint *string `ddl:"parameter,single_quotes" sql:"OAUTH_AUTHORIZATION_ENDPOINT"` @@ -300,7 +287,6 @@ type ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSet struct { OauthRefreshTokenValidity *int `ddl:"parameter" sql:"OAUTH_REFRESH_TOKEN_VALIDITY"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } - type ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationUnset struct { Enabled *bool `ddl:"keyword" sql:"ENABLED"` Comment *bool `ddl:"keyword" sql:"COMMENT"` @@ -317,7 +303,6 @@ type AlterApiAuthenticationWithJwtBearerFlowSecurityIntegrationOptions struct { Set *ApiAuthenticationWithJwtBearerFlowIntegrationSet `ddl:"list,no_parentheses" sql:"SET"` Unset *ApiAuthenticationWithJwtBearerFlowIntegrationUnset `ddl:"list,no_parentheses" sql:"UNSET"` } - type ApiAuthenticationWithJwtBearerFlowIntegrationSet struct { Enabled *bool `ddl:"parameter" sql:"ENABLED"` OauthAuthorizationEndpoint *string `ddl:"parameter,single_quotes" sql:"OAUTH_AUTHORIZATION_ENDPOINT"` @@ -330,7 +315,6 @@ type ApiAuthenticationWithJwtBearerFlowIntegrationSet struct { OauthRefreshTokenValidity *int `ddl:"parameter" sql:"OAUTH_REFRESH_TOKEN_VALIDITY"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } - type ApiAuthenticationWithJwtBearerFlowIntegrationUnset struct { Enabled *bool `ddl:"keyword" sql:"ENABLED"` Comment *bool `ddl:"keyword" sql:"COMMENT"` @@ -347,7 +331,6 @@ type AlterExternalOauthSecurityIntegrationOptions struct { Set *ExternalOauthIntegrationSet `ddl:"list,no_parentheses" sql:"SET"` Unset *ExternalOauthIntegrationUnset `ddl:"list,no_parentheses" sql:"UNSET"` } - type ExternalOauthIntegrationSet struct { Enabled *bool `ddl:"parameter" sql:"ENABLED"` ExternalOauthType *ExternalOauthSecurityIntegrationTypeOption `ddl:"parameter" sql:"EXTERNAL_OAUTH_TYPE"` @@ -362,9 +345,9 @@ type ExternalOauthIntegrationSet struct { ExternalOauthAudienceList *AudienceList `ddl:"parameter,parentheses" sql:"EXTERNAL_OAUTH_AUDIENCE_LIST"` ExternalOauthAnyRoleMode *ExternalOauthSecurityIntegrationAnyRoleModeOption `ddl:"parameter" sql:"EXTERNAL_OAUTH_ANY_ROLE_MODE"` ExternalOauthScopeDelimiter *string `ddl:"parameter,single_quotes" sql:"EXTERNAL_OAUTH_SCOPE_DELIMITER"` - Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` + ExternalOauthScopeMappingAttribute *string `ddl:"parameter,single_quotes" sql:"EXTERNAL_OAUTH_SCOPE_MAPPING_ATTRIBUTE"` + Comment *StringAllowEmpty `ddl:"parameter" sql:"COMMENT"` } - type ExternalOauthIntegrationUnset struct { Enabled *bool `ddl:"keyword" sql:"ENABLED"` ExternalOauthAudienceList *bool `ddl:"keyword" sql:"EXTERNAL_OAUTH_AUDIENCE_LIST"` @@ -381,7 +364,6 @@ type AlterOauthForPartnerApplicationsSecurityIntegrationOptions struct { Set *OauthForPartnerApplicationsIntegrationSet `ddl:"list,no_parentheses" sql:"SET"` Unset *OauthForPartnerApplicationsIntegrationUnset `ddl:"list,no_parentheses" sql:"UNSET"` } - type OauthForPartnerApplicationsIntegrationSet struct { Enabled *bool `ddl:"parameter" sql:"ENABLED"` OauthIssueRefreshTokens *bool `ddl:"parameter" sql:"OAUTH_ISSUE_REFRESH_TOKENS"` @@ -391,7 +373,6 @@ type OauthForPartnerApplicationsIntegrationSet struct { BlockedRolesList *BlockedRolesList `ddl:"parameter,parentheses" sql:"BLOCKED_ROLES_LIST"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } - type OauthForPartnerApplicationsIntegrationUnset struct { Enabled *bool `ddl:"keyword" sql:"ENABLED"` OauthUseSecondaryRoles *bool `ddl:"keyword" sql:"OAUTH_USE_SECONDARY_ROLES"` @@ -408,7 +389,6 @@ type AlterOauthForCustomClientsSecurityIntegrationOptions struct { Set *OauthForCustomClientsIntegrationSet `ddl:"list,no_parentheses" sql:"SET"` Unset *OauthForCustomClientsIntegrationUnset `ddl:"list,no_parentheses" sql:"UNSET"` } - type OauthForCustomClientsIntegrationSet struct { Enabled *bool `ddl:"parameter" sql:"ENABLED"` OauthRedirectUri *string `ddl:"parameter,single_quotes" sql:"OAUTH_REDIRECT_URI"` @@ -424,7 +404,6 @@ type OauthForCustomClientsIntegrationSet struct { OauthClientRsaPublicKey2 *string `ddl:"parameter,single_quotes" sql:"OAUTH_CLIENT_RSA_PUBLIC_KEY_2"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } - type OauthForCustomClientsIntegrationUnset struct { Enabled *bool `ddl:"keyword" sql:"ENABLED"` NetworkPolicy *bool `ddl:"keyword" sql:"NETWORK_POLICY"` @@ -445,7 +424,6 @@ type AlterSaml2SecurityIntegrationOptions struct { Unset *Saml2IntegrationUnset `ddl:"list,no_parentheses" sql:"UNSET"` RefreshSaml2SnowflakePrivateKey *bool `ddl:"keyword" sql:"REFRESH SAML2_SNOWFLAKE_PRIVATE_KEY"` } - type Saml2IntegrationSet struct { Enabled *bool `ddl:"parameter" sql:"ENABLED"` Saml2Issuer *string `ddl:"parameter,single_quotes" sql:"SAML2_ISSUER"` @@ -465,7 +443,6 @@ type Saml2IntegrationSet struct { Saml2SnowflakeAcsUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_SNOWFLAKE_ACS_URL"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } - type Saml2IntegrationUnset struct { Saml2ForceAuthn *bool `ddl:"keyword" sql:"SAML2_FORCE_AUTHN"` Saml2RequestedNameidFormat *bool `ddl:"keyword" sql:"SAML2_REQUESTED_NAMEID_FORMAT"` @@ -484,14 +461,12 @@ type AlterScimSecurityIntegrationOptions struct { Set *ScimIntegrationSet `ddl:"list,no_parentheses" sql:"SET"` Unset *ScimIntegrationUnset `ddl:"list,no_parentheses" sql:"UNSET"` } - type ScimIntegrationSet struct { Enabled *bool `ddl:"parameter" sql:"ENABLED"` NetworkPolicy *AccountObjectIdentifier `ddl:"identifier,equals" sql:"NETWORK_POLICY"` SyncPassword *bool `ddl:"parameter" sql:"SYNC_PASSWORD"` Comment *StringAllowEmpty `ddl:"parameter" sql:"COMMENT"` } - type ScimIntegrationUnset struct { Enabled *bool `ddl:"keyword" sql:"ENABLED"` NetworkPolicy *bool `ddl:"keyword" sql:"NETWORK_POLICY"` @@ -512,14 +487,12 @@ type DescribeSecurityIntegrationOptions struct { securityIntegration bool `ddl:"static" sql:"SECURITY INTEGRATION"` name AccountObjectIdentifier `ddl:"identifier"` } - type securityIntegrationDescRow struct { Property string `db:"property"` PropertyType string `db:"property_type"` PropertyValue string `db:"property_value"` PropertyDefault string `db:"property_default"` } - type SecurityIntegrationProperty struct { Name string Type string @@ -541,7 +514,6 @@ type ShowSecurityIntegrationOptions struct { securityIntegrations bool `ddl:"static" sql:"SECURITY INTEGRATIONS"` Like *Like `ddl:"keyword" sql:"LIKE"` } - type securityIntegrationShowRow struct { Name string `db:"name"` Type string `db:"type"` @@ -550,7 +522,6 @@ type securityIntegrationShowRow struct { Comment sql.NullString `db:"comment"` CreatedOn time.Time `db:"created_on"` } - type SecurityIntegration struct { Name string IntegrationType string diff --git a/pkg/sdk/security_integrations_gen_test.go b/pkg/sdk/security_integrations_gen_test.go index 940c9956fe..6da118b3ea 100644 --- a/pkg/sdk/security_integrations_gen_test.go +++ b/pkg/sdk/security_integrations_gen_test.go @@ -776,7 +776,7 @@ func TestSecurityIntegrations_AlterExternalOauth(t *testing.T) { assertOptsInvalidJoinedErrors(t, opts, errAtLeastOneOf("AlterExternalOauthSecurityIntegrationOptions.Set", "Enabled", "ExternalOauthType", "ExternalOauthIssuer", "ExternalOauthTokenUserMappingClaim", "ExternalOauthSnowflakeUserMappingAttribute", "ExternalOauthJwsKeysUrl", "ExternalOauthBlockedRolesList", "ExternalOauthAllowedRolesList", "ExternalOauthRsaPublicKey", "ExternalOauthRsaPublicKey2", "ExternalOauthAudienceList", - "ExternalOauthAnyRoleMode", "ExternalOauthScopeDelimiter", "Comment")) + "ExternalOauthAnyRoleMode", "ExternalOauthScopeDelimiter", "ExternalOauthScopeMappingAttribute", "Comment")) }) t.Run("validation: at least one of the fields [opts.Unset.*] should be set", func(t *testing.T) { @@ -845,12 +845,13 @@ func TestSecurityIntegrations_AlterExternalOauth(t *testing.T) { ExternalOauthAudienceList: &AudienceList{AudienceList: []AudienceListItem{{Item: "foo"}}}, ExternalOauthAnyRoleMode: Pointer(ExternalOauthSecurityIntegrationAnyRoleModeDisable), ExternalOauthScopeDelimiter: Pointer(" "), - Comment: Pointer("foo"), + ExternalOauthScopeMappingAttribute: Pointer("foo"), + Comment: Pointer(StringAllowEmpty{Value: "foo"}), } assertOptsValidAndSQLEquals(t, opts, "ALTER SECURITY INTEGRATION %s SET ENABLED = true, EXTERNAL_OAUTH_TYPE = CUSTOM, EXTERNAL_OAUTH_ISSUER = 'foo',"+ " EXTERNAL_OAUTH_TOKEN_USER_MAPPING_CLAIM = ('foo'), EXTERNAL_OAUTH_SNOWFLAKE_USER_MAPPING_ATTRIBUTE = 'EMAIL_ADDRESS', EXTERNAL_OAUTH_ALLOWED_ROLES_LIST = (%s),"+ " EXTERNAL_OAUTH_RSA_PUBLIC_KEY = 'foo', EXTERNAL_OAUTH_RSA_PUBLIC_KEY_2 = 'foo', EXTERNAL_OAUTH_AUDIENCE_LIST = ('foo'), EXTERNAL_OAUTH_ANY_ROLE_MODE = DISABLE,"+ - " EXTERNAL_OAUTH_SCOPE_DELIMITER = ' ', COMMENT = 'foo'", id.FullyQualifiedName(), roleID.FullyQualifiedName()) + " EXTERNAL_OAUTH_SCOPE_DELIMITER = ' ', EXTERNAL_OAUTH_SCOPE_MAPPING_ATTRIBUTE = 'foo', COMMENT = 'foo'", id.FullyQualifiedName(), roleID.FullyQualifiedName()) opts.Set = &ExternalOauthIntegrationSet{ ExternalOauthBlockedRolesList: &BlockedRolesList{BlockedRolesList: []AccountObjectIdentifier{roleID}}, ExternalOauthJwsKeysUrl: []JwsKeysUrl{{JwsKeyUrl: "foo"}}, @@ -867,6 +868,14 @@ func TestSecurityIntegrations_AlterExternalOauth(t *testing.T) { assertOptsValidAndSQLEquals(t, opts, "ALTER SECURITY INTEGRATION %s UNSET ENABLED, EXTERNAL_OAUTH_AUDIENCE_LIST", id.FullyQualifiedName()) }) + t.Run("set empty comment", func(t *testing.T) { + opts := defaultOpts() + opts.Set = &ExternalOauthIntegrationSet{ + Comment: Pointer(StringAllowEmpty{Value: ""}), + } + assertOptsValidAndSQLEquals(t, opts, "ALTER SECURITY INTEGRATION %s SET COMMENT = ''", id.FullyQualifiedName()) + }) + t.Run("set tags", func(t *testing.T) { opts := defaultOpts() opts.SetTags = []TagAssociation{ diff --git a/pkg/sdk/security_integrations_impl_gen.go b/pkg/sdk/security_integrations_impl_gen.go index c2244cb30d..9ea4453311 100644 --- a/pkg/sdk/security_integrations_impl_gen.go +++ b/pkg/sdk/security_integrations_impl_gen.go @@ -206,21 +206,25 @@ func (r *CreateExternalOauthSecurityIntegrationRequest) toOpts() *CreateExternal ExternalOauthScopeMappingAttribute: r.ExternalOauthScopeMappingAttribute, Comment: r.Comment, } + if r.ExternalOauthBlockedRolesList != nil { opts.ExternalOauthBlockedRolesList = &BlockedRolesList{ BlockedRolesList: r.ExternalOauthBlockedRolesList.BlockedRolesList, } } + if r.ExternalOauthAllowedRolesList != nil { opts.ExternalOauthAllowedRolesList = &AllowedRolesList{ AllowedRolesList: r.ExternalOauthAllowedRolesList.AllowedRolesList, } } + if r.ExternalOauthAudienceList != nil { opts.ExternalOauthAudienceList = &AudienceList{ AudienceList: r.ExternalOauthAudienceList.AudienceList, } } + return opts } @@ -238,11 +242,13 @@ func (r *CreateOauthForPartnerApplicationsSecurityIntegrationRequest) toOpts() * Comment: r.Comment, } + if r.BlockedRolesList != nil { opts.BlockedRolesList = &BlockedRolesList{ BlockedRolesList: r.BlockedRolesList.BlockedRolesList, } } + return opts } @@ -265,16 +271,19 @@ func (r *CreateOauthForCustomClientsSecurityIntegrationRequest) toOpts() *Create OauthClientRsaPublicKey2: r.OauthClientRsaPublicKey2, Comment: r.Comment, } + if r.PreAuthorizedRolesList != nil { opts.PreAuthorizedRolesList = &PreAuthorizedRolesList{ PreAuthorizedRolesList: r.PreAuthorizedRolesList.PreAuthorizedRolesList, } } + if r.BlockedRolesList != nil { opts.BlockedRolesList = &BlockedRolesList{ BlockedRolesList: r.BlockedRolesList.BlockedRolesList, } } + return opts } @@ -326,6 +335,7 @@ func (r *AlterApiAuthenticationWithClientCredentialsFlowSecurityIntegrationReque SetTags: r.SetTags, UnsetTags: r.UnsetTags, } + if r.Set != nil { opts.Set = &ApiAuthenticationWithClientCredentialsFlowIntegrationSet{ Enabled: r.Set.Enabled, @@ -340,12 +350,14 @@ func (r *AlterApiAuthenticationWithClientCredentialsFlowSecurityIntegrationReque Comment: r.Set.Comment, } } + if r.Unset != nil { opts.Unset = &ApiAuthenticationWithClientCredentialsFlowIntegrationUnset{ Enabled: r.Unset.Enabled, Comment: r.Unset.Comment, } } + return opts } @@ -356,6 +368,7 @@ func (r *AlterApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegration SetTags: r.SetTags, UnsetTags: r.UnsetTags, } + if r.Set != nil { opts.Set = &ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSet{ Enabled: r.Set.Enabled, @@ -370,12 +383,14 @@ func (r *AlterApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegration Comment: r.Set.Comment, } } + if r.Unset != nil { opts.Unset = &ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationUnset{ Enabled: r.Unset.Enabled, Comment: r.Unset.Comment, } } + return opts } @@ -386,6 +401,7 @@ func (r *AlterApiAuthenticationWithJwtBearerFlowSecurityIntegrationRequest) toOp SetTags: r.SetTags, UnsetTags: r.UnsetTags, } + if r.Set != nil { opts.Set = &ApiAuthenticationWithJwtBearerFlowIntegrationSet{ Enabled: r.Set.Enabled, @@ -400,12 +416,14 @@ func (r *AlterApiAuthenticationWithJwtBearerFlowSecurityIntegrationRequest) toOp Comment: r.Set.Comment, } } + if r.Unset != nil { opts.Unset = &ApiAuthenticationWithJwtBearerFlowIntegrationUnset{ Enabled: r.Unset.Enabled, Comment: r.Unset.Comment, } } + return opts } @@ -416,6 +434,7 @@ func (r *AlterExternalOauthSecurityIntegrationRequest) toOpts() *AlterExternalOa SetTags: r.SetTags, UnsetTags: r.UnsetTags, } + if r.Set != nil { opts.Set = &ExternalOauthIntegrationSet{ Enabled: r.Set.Enabled, @@ -428,32 +447,39 @@ func (r *AlterExternalOauthSecurityIntegrationRequest) toOpts() *AlterExternalOa ExternalOauthRsaPublicKey: r.Set.ExternalOauthRsaPublicKey, ExternalOauthRsaPublicKey2: r.Set.ExternalOauthRsaPublicKey2, - ExternalOauthAnyRoleMode: r.Set.ExternalOauthAnyRoleMode, - ExternalOauthScopeDelimiter: r.Set.ExternalOauthScopeDelimiter, - Comment: r.Set.Comment, + ExternalOauthAnyRoleMode: r.Set.ExternalOauthAnyRoleMode, + ExternalOauthScopeDelimiter: r.Set.ExternalOauthScopeDelimiter, + ExternalOauthScopeMappingAttribute: r.Set.ExternalOauthScopeMappingAttribute, + Comment: r.Set.Comment, } + if r.Set.ExternalOauthBlockedRolesList != nil { opts.Set.ExternalOauthBlockedRolesList = &BlockedRolesList{ BlockedRolesList: r.Set.ExternalOauthBlockedRolesList.BlockedRolesList, } } + if r.Set.ExternalOauthAllowedRolesList != nil { opts.Set.ExternalOauthAllowedRolesList = &AllowedRolesList{ AllowedRolesList: r.Set.ExternalOauthAllowedRolesList.AllowedRolesList, } } + if r.Set.ExternalOauthAudienceList != nil { opts.Set.ExternalOauthAudienceList = &AudienceList{ AudienceList: r.Set.ExternalOauthAudienceList.AudienceList, } } + } + if r.Unset != nil { opts.Unset = &ExternalOauthIntegrationUnset{ Enabled: r.Unset.Enabled, ExternalOauthAudienceList: r.Unset.ExternalOauthAudienceList, } } + return opts } @@ -464,6 +490,7 @@ func (r *AlterOauthForPartnerApplicationsSecurityIntegrationRequest) toOpts() *A SetTags: r.SetTags, UnsetTags: r.UnsetTags, } + if r.Set != nil { opts.Set = &OauthForPartnerApplicationsIntegrationSet{ Enabled: r.Set.Enabled, @@ -474,18 +501,22 @@ func (r *AlterOauthForPartnerApplicationsSecurityIntegrationRequest) toOpts() *A Comment: r.Set.Comment, } + if r.Set.BlockedRolesList != nil { opts.Set.BlockedRolesList = &BlockedRolesList{ BlockedRolesList: r.Set.BlockedRolesList.BlockedRolesList, } } + } + if r.Unset != nil { opts.Unset = &OauthForPartnerApplicationsIntegrationUnset{ Enabled: r.Unset.Enabled, OauthUseSecondaryRoles: r.Unset.OauthUseSecondaryRoles, } } + return opts } @@ -496,6 +527,7 @@ func (r *AlterOauthForCustomClientsSecurityIntegrationRequest) toOpts() *AlterOa SetTags: r.SetTags, UnsetTags: r.UnsetTags, } + if r.Set != nil { opts.Set = &OauthForCustomClientsIntegrationSet{ Enabled: r.Set.Enabled, @@ -511,17 +543,21 @@ func (r *AlterOauthForCustomClientsSecurityIntegrationRequest) toOpts() *AlterOa OauthClientRsaPublicKey2: r.Set.OauthClientRsaPublicKey2, Comment: r.Set.Comment, } + if r.Set.PreAuthorizedRolesList != nil { opts.Set.PreAuthorizedRolesList = &PreAuthorizedRolesList{ PreAuthorizedRolesList: r.Set.PreAuthorizedRolesList.PreAuthorizedRolesList, } } + if r.Set.BlockedRolesList != nil { opts.Set.BlockedRolesList = &BlockedRolesList{ BlockedRolesList: r.Set.BlockedRolesList.BlockedRolesList, } } + } + if r.Unset != nil { opts.Unset = &OauthForCustomClientsIntegrationUnset{ Enabled: r.Unset.Enabled, @@ -531,6 +567,7 @@ func (r *AlterOauthForCustomClientsSecurityIntegrationRequest) toOpts() *AlterOa OauthUseSecondaryRoles: r.Unset.OauthUseSecondaryRoles, } } + return opts } @@ -543,6 +580,7 @@ func (r *AlterSaml2SecurityIntegrationRequest) toOpts() *AlterSaml2SecurityInteg RefreshSaml2SnowflakePrivateKey: r.RefreshSaml2SnowflakePrivateKey, } + if r.Set != nil { opts.Set = &Saml2IntegrationSet{ Enabled: r.Set.Enabled, @@ -564,6 +602,7 @@ func (r *AlterSaml2SecurityIntegrationRequest) toOpts() *AlterSaml2SecurityInteg Comment: r.Set.Comment, } } + if r.Unset != nil { opts.Unset = &Saml2IntegrationUnset{ Saml2ForceAuthn: r.Unset.Saml2ForceAuthn, @@ -572,6 +611,7 @@ func (r *AlterSaml2SecurityIntegrationRequest) toOpts() *AlterSaml2SecurityInteg Comment: r.Unset.Comment, } } + return opts } @@ -582,6 +622,7 @@ func (r *AlterScimSecurityIntegrationRequest) toOpts() *AlterScimSecurityIntegra SetTags: r.SetTags, UnsetTags: r.UnsetTags, } + if r.Set != nil { opts.Set = &ScimIntegrationSet{ Enabled: r.Set.Enabled, @@ -590,6 +631,7 @@ func (r *AlterScimSecurityIntegrationRequest) toOpts() *AlterScimSecurityIntegra Comment: r.Set.Comment, } } + if r.Unset != nil { opts.Unset = &ScimIntegrationUnset{ Enabled: r.Unset.Enabled, @@ -597,6 +639,7 @@ func (r *AlterScimSecurityIntegrationRequest) toOpts() *AlterScimSecurityIntegra SyncPassword: r.Unset.SyncPassword, } } + return opts } diff --git a/pkg/sdk/security_integrations_validations_gen.go b/pkg/sdk/security_integrations_validations_gen.go index 2e6129b157..9da1a7e79b 100644 --- a/pkg/sdk/security_integrations_validations_gen.go +++ b/pkg/sdk/security_integrations_validations_gen.go @@ -239,8 +239,8 @@ func (opts *AlterExternalOauthSecurityIntegrationOptions) validate() error { if everyValueSet(opts.Set.ExternalOauthJwsKeysUrl, opts.Set.ExternalOauthRsaPublicKey2) { errs = append(errs, errOneOf("AlterExternalOauthSecurityIntegrationOptions.Set", "ExternalOauthJwsKeysUrl", "ExternalOauthRsaPublicKey2")) } - if !anyValueSet(opts.Set.Enabled, opts.Set.ExternalOauthType, opts.Set.ExternalOauthIssuer, opts.Set.ExternalOauthTokenUserMappingClaim, opts.Set.ExternalOauthSnowflakeUserMappingAttribute, opts.Set.ExternalOauthJwsKeysUrl, opts.Set.ExternalOauthBlockedRolesList, opts.Set.ExternalOauthAllowedRolesList, opts.Set.ExternalOauthRsaPublicKey, opts.Set.ExternalOauthRsaPublicKey2, opts.Set.ExternalOauthAudienceList, opts.Set.ExternalOauthAnyRoleMode, opts.Set.ExternalOauthScopeDelimiter, opts.Set.Comment) { - errs = append(errs, errAtLeastOneOf("AlterExternalOauthSecurityIntegrationOptions.Set", "Enabled", "ExternalOauthType", "ExternalOauthIssuer", "ExternalOauthTokenUserMappingClaim", "ExternalOauthSnowflakeUserMappingAttribute", "ExternalOauthJwsKeysUrl", "ExternalOauthBlockedRolesList", "ExternalOauthAllowedRolesList", "ExternalOauthRsaPublicKey", "ExternalOauthRsaPublicKey2", "ExternalOauthAudienceList", "ExternalOauthAnyRoleMode", "ExternalOauthScopeDelimiter", "Comment")) + if !anyValueSet(opts.Set.Enabled, opts.Set.ExternalOauthType, opts.Set.ExternalOauthIssuer, opts.Set.ExternalOauthTokenUserMappingClaim, opts.Set.ExternalOauthSnowflakeUserMappingAttribute, opts.Set.ExternalOauthJwsKeysUrl, opts.Set.ExternalOauthBlockedRolesList, opts.Set.ExternalOauthAllowedRolesList, opts.Set.ExternalOauthRsaPublicKey, opts.Set.ExternalOauthRsaPublicKey2, opts.Set.ExternalOauthAudienceList, opts.Set.ExternalOauthAnyRoleMode, opts.Set.ExternalOauthScopeDelimiter, opts.Set.ExternalOauthScopeMappingAttribute, opts.Set.Comment) { + errs = append(errs, errAtLeastOneOf("AlterExternalOauthSecurityIntegrationOptions.Set", "Enabled", "ExternalOauthType", "ExternalOauthIssuer", "ExternalOauthTokenUserMappingClaim", "ExternalOauthSnowflakeUserMappingAttribute", "ExternalOauthJwsKeysUrl", "ExternalOauthBlockedRolesList", "ExternalOauthAllowedRolesList", "ExternalOauthRsaPublicKey", "ExternalOauthRsaPublicKey2", "ExternalOauthAudienceList", "ExternalOauthAnyRoleMode", "ExternalOauthScopeDelimiter", "ExternalOauthScopeMappingAttribute", "Comment")) } } if valueSet(opts.Unset) { diff --git a/pkg/sdk/testint/security_integrations_gen_integration_test.go b/pkg/sdk/testint/security_integrations_gen_integration_test.go index 993d6500a4..a113dc942b 100644 --- a/pkg/sdk/testint/security_integrations_gen_integration_test.go +++ b/pkg/sdk/testint/security_integrations_gen_integration_test.go @@ -878,7 +878,7 @@ func TestInt_SecurityIntegrations(t *testing.T) { WithExternalOauthAudienceList(sdk.AudienceListRequest{AudienceList: []sdk.AudienceListItem{{Item: "foo"}}}). WithExternalOauthAnyRoleMode(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable). WithExternalOauthScopeDelimiter(" "). - WithComment("foo"), + WithComment(sdk.StringAllowEmpty{Value: "foo"}), ) err := client.SecurityIntegrations.AlterExternalOauth(ctx, setRequest) require.NoError(t, err) From 113f69473eba13a70e2430457eee16f561ce5ec2 Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Wed, 3 Jul 2024 14:38:14 +0200 Subject: [PATCH 05/14] Add and fix tests --- docs/resources/external_oauth_integration.md | 281 +++++++++++++++-- docs/resources/scim_integration.md | 2 +- .../resource.tf | 38 ++- go.mod | 1 - .../helpers/security_integration_client.go | 8 + pkg/provider/resources/resources.go | 83 ++--- pkg/resources/external_oauth_integration.go | 80 +++-- ...ernal_oauth_integration_acceptance_test.go | 296 +++++++++++++++++- .../basic/test.tf | 5 +- .../basic/variables.tf | 7 +- .../test.tf | 2 +- .../variables.tf | 2 +- .../test.tf | 2 +- .../variables.tf | 2 +- pkg/sdk/security_integrations_impl_gen.go | 3 - 15 files changed, 669 insertions(+), 143 deletions(-) diff --git a/docs/resources/external_oauth_integration.md b/docs/resources/external_oauth_integration.md index db2edb22e0..1e387b44e7 100644 --- a/docs/resources/external_oauth_integration.md +++ b/docs/resources/external_oauth_integration.md @@ -2,25 +2,57 @@ page_title: "snowflake_external_oauth_integration Resource - terraform-provider-snowflake" subcategory: "" description: |- - An External OAuth security integration allows a client to use a third-party authorization server to obtain the access tokens needed to interact with Snowflake. + --- # snowflake_external_oauth_integration (Resource) -An External OAuth security integration allows a client to use a third-party authorization server to obtain the access tokens needed to interact with Snowflake. + ## Example Usage ```terraform -resource "snowflake_external_oauth_integration" "azure" { - name = "AZURE_POWERBI" - type = "AZURE" - enabled = true - issuer = "https://sts.windows.net/00000000-0000-0000-0000-000000000000" - snowflake_user_mapping_attribute = "LOGIN_NAME" - jws_keys_urls = ["https://login.windows.net/common/discovery/keys"] - audience_urls = ["https://analysis.windows.net/powerbi/connector/Snowflake"] - token_user_mapping_claims = ["upn"] +# basic resource +resource "snowflake_external_oauth_integration" "test" { + enabled = true + external_oauth_issuer = "issuer" + external_oauth_snowflake_user_mapping_attribute = "LOGIN_NAME" + external_oauth_token_user_mapping_claim = ["upn"] + name = "test" + external_oauth_type = "CUSTOM" +} +# resource with all fields set (jws keys url and allowed roles) +resource "snowflake_external_oauth_integration" "test" { + comment = "comment" + enabled = true + external_oauth_allowed_roles_list = ["user1"] + external_oauth_any_role_mode = "ENABLED" + external_oauth_audience_list = ["https://example.com"] + external_oauth_issuer = "issuer" + external_oauth_jws_keys_url = ["https://example.com"] + external_oauth_scope_delimiter = "," + external_oauth_scope_mapping_attribute = "scope" + external_oauth_snowflake_user_mapping_attribute = "LOGIN_NAME" + external_oauth_token_user_mapping_claim = ["upn"] + name = "test" + external_oauth_type = "CUSTOM" +} +# resource with all fields set (rsa public keys and blocked roles) +resource "snowflake_external_oauth_integration" "test" { + comment = "comment" + enabled = true + external_oauth_any_role_mode = "ENABLED" + external_oauth_audience_list = ["https://example.com"] + external_oauth_blocked_roles_list = ["user1"] + external_oauth_issuer = "issuer" + external_oauth_rsa_public_key = file("key.pem") + external_oauth_rsa_public_key_2 = file("key2.pem") + external_oauth_scope_delimiter = "," + external_oauth_scope_mapping_attribute = "scope" + external_oauth_snowflake_user_mapping_attribute = "LOGIN_NAME" + external_oauth_token_user_mapping_claim = ["upn"] + name = "test" + external_oauth_type = "CUSTOM" } ``` @@ -30,34 +62,231 @@ resource "snowflake_external_oauth_integration" "azure" { ### Required - `enabled` (Boolean) Specifies whether to initiate operation of the integration or suspend it. -- `issuer` (String) Specifies the URL to define the OAuth 2.0 authorization server. +- `external_oauth_issuer` (String) Specifies the URL to define the OAuth 2.0 authorization server. +- `external_oauth_snowflake_user_mapping_attribute` (String) Indicates which Snowflake user record attribute should be used to map the access token to a Snowflake user record. Valid options are: [LOGIN_NAME EMAIL_ADDRESS] +- `external_oauth_token_user_mapping_claim` (Set of String) Specifies the access token claim or claims that can be used to map the access token to a Snowflake user record. +- `external_oauth_type` (String) Specifies the OAuth 2.0 authorization server to be Okta, Microsoft Azure AD, Ping Identity PingFederate, or a Custom OAuth 2.0 authorization server. Valid options are: [OKTA AZURE PING_FEDERATE CUSTOM] - `name` (String) Specifies the name of the External Oath integration. This name follows the rules for Object Identifiers. The name should be unique among security integrations in your account. -- `snowflake_user_mapping_attribute` (String) Indicates which Snowflake user record attribute should be used to map the access token to a Snowflake user record. -- `token_user_mapping_claims` (Set of String) Specifies the access token claim or claims that can be used to map the access token to a Snowflake user record. -- `type` (String) Specifies the OAuth 2.0 authorization server to be Okta, Microsoft Azure AD, Ping Identity PingFederate, or a Custom OAuth 2.0 authorization server. ### Optional -- `allowed_roles` (Set of String) Specifies the list of roles that the client can set as the primary role. -- `any_role_mode` (String) Specifies whether the OAuth client or user can use a role that is not defined in the OAuth access token. -- `audience_urls` (Set of String) Specifies additional values that can be used for the access token's audience validation on top of using the Customer's Snowflake Account URL -- `blocked_roles` (Set of String) Specifies the list of roles that a client cannot set as the primary role. Do not include ACCOUNTADMIN, ORGADMIN or SECURITYADMIN as they are already implicitly enforced and will cause in-place updates. - `comment` (String) Specifies a comment for the OAuth integration. -- `jws_keys_urls` (Set of String) Specifies the endpoint or a list of endpoints from which to download public keys or certificates to validate an External OAuth access token. The maximum number of URLs that can be specified in the list is 3. -- `rsa_public_key` (String) Specifies a Base64-encoded RSA public key, without the -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY----- headers. -- `rsa_public_key_2` (String) Specifies a second RSA public key, without the -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY----- headers. Used for key rotation. -- `scope_delimiter` (String) Specifies the scope delimiter in the authorization token. -- `scope_mapping_attribute` (String) Specifies the access token claim to map the access token to an account role. +- `external_oauth_allowed_roles_list` (Set of String) Specifies the list of roles that the client can set as the primary role. +- `external_oauth_any_role_mode` (String) Specifies whether the OAuth client or user can use a role that is not defined in the OAuth access token. Valid options are: [DISABLE ENABLE ENABLE_FOR_PRIVILEGE] +- `external_oauth_audience_list` (Set of String) Specifies additional values that can be used for the access token's audience validation on top of using the Customer's Snowflake Account URL +- `external_oauth_blocked_roles_list` (Set of String) Specifies the list of roles that a client cannot set as the primary role. By default, this list includes the ACCOUNTADMIN, and SECURITYADMIN roles. To remove these privileged roles from the list, use the ALTER ACCOUNT command to set the EXTERNAL_OAUTH_ADD_PRIVILEGED_ROLES_TO_BLOCKED_LIST account parameter to FALSE. +- `external_oauth_jws_keys_url` (Set of String) Specifies the endpoint or a list of endpoints from which to download public keys or certificates to validate an External OAuth access token. The maximum number of URLs that can be specified in the list is 3. +- `external_oauth_rsa_public_key` (String) Specifies a Base64-encoded RSA public key, without the -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY----- headers. +- `external_oauth_rsa_public_key_2` (String) Specifies a second RSA public key, without the -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY----- headers. Used for key rotation. +- `external_oauth_scope_delimiter` (String) Specifies the scope delimiter in the authorization token. +- `external_oauth_scope_mapping_attribute` (String) Specifies the access token claim to map the access token to an account role. ### Read-Only -- `created_on` (String) Date and time when the External OAUTH integration was created. +- `describe_output` (List of Object) Outputs the result of `DESCRIBE SECURITY INTEGRATIONS` for the given security integration. (see [below for nested schema](#nestedatt--describe_output)) - `id` (String) The ID of this resource. +- `parameters` (List of Object) Paramteres related to this security integration. (see [below for nested schema](#nestedatt--parameters)) +- `show_output` (List of Object) Outputs the result of `SHOW SECURITY INTEGRATIONS` for the given security integration. (see [below for nested schema](#nestedatt--show_output)) + + +### Nested Schema for `describe_output` + +Read-Only: + +- `comment` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--comment)) +- `enabled` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--enabled)) +- `external_oauth_allowed_roles_list` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--external_oauth_allowed_roles_list)) +- `external_oauth_any_role_mode` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--external_oauth_any_role_mode)) +- `external_oauth_audience_list` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--external_oauth_audience_list)) +- `external_oauth_blocked_roles_list` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--external_oauth_blocked_roles_list)) +- `external_oauth_issuer` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--external_oauth_issuer)) +- `external_oauth_jws_keys_url` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--external_oauth_jws_keys_url)) +- `external_oauth_rsa_public_key` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--external_oauth_rsa_public_key)) +- `external_oauth_rsa_public_key_2` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--external_oauth_rsa_public_key_2)) +- `external_oauth_scope_delimiter` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--external_oauth_scope_delimiter)) +- `external_oauth_snowflake_user_mapping_attribute` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--external_oauth_snowflake_user_mapping_attribute)) +- `external_oauth_token_user_mapping_claim` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--external_oauth_token_user_mapping_claim)) + + +### Nested Schema for `describe_output.comment` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.enabled` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.external_oauth_allowed_roles_list` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.external_oauth_any_role_mode` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.external_oauth_audience_list` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.external_oauth_blocked_roles_list` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.external_oauth_issuer` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.external_oauth_jws_keys_url` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.external_oauth_rsa_public_key` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.external_oauth_rsa_public_key_2` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.external_oauth_scope_delimiter` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.external_oauth_snowflake_user_mapping_attribute` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.external_oauth_token_user_mapping_claim` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + + +### Nested Schema for `parameters` + +Read-Only: + +- `external_oauth_add_privileged_roles_to_blocked_list` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--external_oauth_add_privileged_roles_to_blocked_list)) + + +### Nested Schema for `parameters.external_oauth_add_privileged_roles_to_blocked_list` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + + + + +### Nested Schema for `show_output` + +Read-Only: + +- `category` (String) +- `comment` (String) +- `created_on` (String) +- `enabled` (Boolean) +- `integration_type` (String) +- `name` (String) ## Import Import is supported using the following syntax: ```shell -terraform import snowflake_external_oauth_integration.example name +terraform import snowflake_external_oauth_integration.example "name" ``` diff --git a/docs/resources/scim_integration.md b/docs/resources/scim_integration.md index f07d8bee00..e4a0dd1086 100644 --- a/docs/resources/scim_integration.md +++ b/docs/resources/scim_integration.md @@ -2,7 +2,7 @@ page_title: "snowflake_scim_integration Resource - terraform-provider-snowflake" subcategory: "" description: |- - + --- # snowflake_scim_integration (Resource) diff --git a/examples/resources/snowflake_external_oauth_integration/resource.tf b/examples/resources/snowflake_external_oauth_integration/resource.tf index 08df19b063..1e99d38bf3 100644 --- a/examples/resources/snowflake_external_oauth_integration/resource.tf +++ b/examples/resources/snowflake_external_oauth_integration/resource.tf @@ -3,42 +3,40 @@ resource "snowflake_external_oauth_integration" "test" { enabled = true external_oauth_issuer = "issuer" external_oauth_snowflake_user_mapping_attribute = "LOGIN_NAME" - external_oauth_token_user_mapping_claims = ["foo"] + external_oauth_token_user_mapping_claim = ["upn"] name = "test" external_oauth_type = "CUSTOM" } -# resource with all fields set (jws keys url flow) +# resource with all fields set (jws keys url and allowed roles) resource "snowflake_external_oauth_integration" "test" { - comment = "foo" + comment = "comment" enabled = true - external_oauth_allowed_roles_list = ["foo"] + external_oauth_allowed_roles_list = ["user1"] external_oauth_any_role_mode = "ENABLED" - external_oauth_audience_list = ["foo"] - external_oauth_blocked_roles_list = ["bar"] + external_oauth_audience_list = ["https://example.com"] external_oauth_issuer = "issuer" external_oauth_jws_keys_url = ["https://example.com"] external_oauth_scope_delimiter = "," - external_oauth_scope_mapping_attribute = "LOGIN_NAME" - external_oauth_snowflake_user_mapping_attribute = "foo" - external_oauth_token_user_mapping_claims = ["foo"] - name = "foo" + external_oauth_scope_mapping_attribute = "scope" + external_oauth_snowflake_user_mapping_attribute = "LOGIN_NAME" + external_oauth_token_user_mapping_claim = ["upn"] + name = "test" external_oauth_type = "CUSTOM" } -# resource with all fields set (rsa public key flow) +# resource with all fields set (rsa public keys and blocked roles) resource "snowflake_external_oauth_integration" "test" { - comment = "foo" + comment = "comment" enabled = true - external_oauth_allowed_roles_list = ["foo"] external_oauth_any_role_mode = "ENABLED" - external_oauth_audience_list = ["foo"] - external_oauth_blocked_roles_list = ["bar"] + external_oauth_audience_list = ["https://example.com"] + external_oauth_blocked_roles_list = ["user1"] external_oauth_issuer = "issuer" external_oauth_rsa_public_key = file("key.pem") external_oauth_rsa_public_key_2 = file("key2.pem") - external_oauth_scope_delimiter = "foo" - external_oauth_scope_mapping_attribute = "LOGIN_NAME" - external_oauth_snowflake_user_mapping_attribute = "foo" - external_oauth_token_user_mapping_claims = ["foo"] - name = "foo" + external_oauth_scope_delimiter = "," + external_oauth_scope_mapping_attribute = "scope" + external_oauth_snowflake_user_mapping_attribute = "LOGIN_NAME" + external_oauth_token_user_mapping_claim = ["upn"] + name = "test" external_oauth_type = "CUSTOM" } diff --git a/go.mod b/go.mod index b5f3159a0b..1ff88672d3 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,6 @@ require ( github.com/avast/retry-go v3.0.0+incompatible github.com/brianvoe/gofakeit/v6 v6.28.0 github.com/buger/jsonparser v1.1.1 - github.com/google/uuid v1.6.0 github.com/gookit/color v1.5.4 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/go-uuid v1.0.3 diff --git a/pkg/acceptance/helpers/security_integration_client.go b/pkg/acceptance/helpers/security_integration_client.go index 08d854fbb4..f85c1042ab 100644 --- a/pkg/acceptance/helpers/security_integration_client.go +++ b/pkg/acceptance/helpers/security_integration_client.go @@ -25,6 +25,14 @@ func (c *SecurityIntegrationClient) client() sdk.SecurityIntegrations { return c.context.client.SecurityIntegrations } +func (c *SecurityIntegrationClient) UpdateExternalOauth(t *testing.T, request *sdk.AlterExternalOauthSecurityIntegrationRequest) { + t.Helper() + ctx := context.Background() + + err := c.client().AlterExternalOauth(ctx, request) + require.NoError(t, err) +} + func (c *SecurityIntegrationClient) CreateSaml2(t *testing.T, id sdk.AccountObjectIdentifier) (*sdk.SecurityIntegration, func()) { t.Helper() return c.CreateSaml2WithRequest(t, sdk.NewCreateSaml2SecurityIntegrationRequest(id, false, c.ids.Alpha(), "https://example.com", "Custom", random.GenerateX509(t))) diff --git a/pkg/provider/resources/resources.go b/pkg/provider/resources/resources.go index daef998c37..4760fec303 100644 --- a/pkg/provider/resources/resources.go +++ b/pkg/provider/resources/resources.go @@ -3,47 +3,48 @@ package resources type resource string const ( - Account resource = "snowflake_account" - Alert resource = "snowflake_alert" - ApiIntegration resource = "snowflake_api_integration" - CortexSearchService resource = "snowflake_cortex_search_service" - DatabaseOld resource = "snowflake_database_old" - Database resource = "snowflake_database" - DatabaseRole resource = "snowflake_database_role" - DynamicTable resource = "snowflake_dynamic_table" - EmailNotificationIntegration resource = "snowflake_email_notification_integration" - ExternalFunction resource = "snowflake_external_function" - ExternalTable resource = "snowflake_external_table" - FailoverGroup resource = "snowflake_failover_group" - FileFormat resource = "snowflake_file_format" - Function resource = "snowflake_function" - ManagedAccount resource = "snowflake_managed_account" - MaskingPolicy resource = "snowflake_masking_policy" - MaterializedView resource = "snowflake_materialized_view" - NetworkPolicy resource = "snowflake_network_policy" - NetworkRule resource = "snowflake_network_rule" - NotificationIntegration resource = "snowflake_notification_integration" - PasswordPolicy resource = "snowflake_password_policy" - Pipe resource = "snowflake_pipe" - Procedure resource = "snowflake_procedure" - ResourceMonitor resource = "snowflake_resource_monitor" - Role resource = "snowflake_role" - RowAccessPolicy resource = "snowflake_row_access_policy" - Schema resource = "snowflake_schema" - ScimSecurityIntegration resource = "snowflake_scim_integration" - SecondaryDatabase resource = "snowflake_secondary_database" - Sequence resource = "snowflake_sequence" - Share resource = "snowflake_share" - SharedDatabase resource = "snowflake_shared_database" - Stage resource = "snowflake_stage" - StorageIntegration resource = "snowflake_storage_integration" - Stream resource = "snowflake_stream" - Table resource = "snowflake_table" - Tag resource = "snowflake_tag" - Task resource = "snowflake_task" - User resource = "snowflake_user" - View resource = "snowflake_view" - Warehouse resource = "snowflake_warehouse" + Account resource = "snowflake_account" + Alert resource = "snowflake_alert" + ApiIntegration resource = "snowflake_api_integration" + CortexSearchService resource = "snowflake_cortex_search_service" + DatabaseOld resource = "snowflake_database_old" + Database resource = "snowflake_database" + DatabaseRole resource = "snowflake_database_role" + DynamicTable resource = "snowflake_dynamic_table" + EmailNotificationIntegration resource = "snowflake_email_notification_integration" + ExternalFunction resource = "snowflake_external_function" + ExternalTable resource = "snowflake_external_table" + ExternalOauthSecurityIntegration resource = "snowflake_external_oauth_security_integration" + FailoverGroup resource = "snowflake_failover_group" + FileFormat resource = "snowflake_file_format" + Function resource = "snowflake_function" + ManagedAccount resource = "snowflake_managed_account" + MaskingPolicy resource = "snowflake_masking_policy" + MaterializedView resource = "snowflake_materialized_view" + NetworkPolicy resource = "snowflake_network_policy" + NetworkRule resource = "snowflake_network_rule" + NotificationIntegration resource = "snowflake_notification_integration" + PasswordPolicy resource = "snowflake_password_policy" + Pipe resource = "snowflake_pipe" + Procedure resource = "snowflake_procedure" + ResourceMonitor resource = "snowflake_resource_monitor" + Role resource = "snowflake_role" + RowAccessPolicy resource = "snowflake_row_access_policy" + Schema resource = "snowflake_schema" + ScimSecurityIntegration resource = "snowflake_scim_integration" + SecondaryDatabase resource = "snowflake_secondary_database" + Sequence resource = "snowflake_sequence" + Share resource = "snowflake_share" + SharedDatabase resource = "snowflake_shared_database" + Stage resource = "snowflake_stage" + StorageIntegration resource = "snowflake_storage_integration" + Stream resource = "snowflake_stream" + Table resource = "snowflake_table" + Tag resource = "snowflake_tag" + Task resource = "snowflake_task" + User resource = "snowflake_user" + View resource = "snowflake_view" + Warehouse resource = "snowflake_warehouse" ) type Resource interface { diff --git a/pkg/resources/external_oauth_integration.go b/pkg/resources/external_oauth_integration.go index dacc1e1536..1a1abc136d 100644 --- a/pkg/resources/external_oauth_integration.go +++ b/pkg/resources/external_oauth_integration.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "reflect" + "slices" "strings" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" @@ -91,7 +92,7 @@ var oauthExternalIntegrationSchema = map[string]*schema.Schema{ Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, Optional: true, - Description: "Specifies the list of roles that a client cannot set as the primary role. By default, this list includes the ACCOUNTADMIN, ORGADMIN, and SECURITYADMIN roles. To remove these privileged roles from the list, use the ALTER ACCOUNT command to set the EXTERNAL_OAUTH_ADD_PRIVILEGED_ROLES_TO_BLOCKED_LIST account parameter to FALSE.", + Description: "Specifies the list of roles that a client cannot set as the primary role. By default, this list includes the ACCOUNTADMIN, and SECURITYADMIN roles. To remove these privileged roles from the list, use the ALTER ACCOUNT command to set the EXTERNAL_OAUTH_ADD_PRIVILEGED_ROLES_TO_BLOCKED_LIST account parameter to FALSE.", DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { params := d.Get(ParametersAttributeName).([]any) var found *sdk.Parameter @@ -183,7 +184,6 @@ func ExternalOauthIntegration() *schema.Resource { DeleteContext: DeleteContextExternalOauthIntegration, Schema: oauthExternalIntegrationSchema, CustomizeDiff: customdiff.All( - ForceNewIfChangeToEmptyString("external_oauth_snowflake_user_mapping_attribute"), ForceNewIfChangeToEmptyString("external_oauth_rsa_public_key"), ForceNewIfChangeToEmptyString("external_oauth_rsa_public_key_2"), ForceNewIfChangeToEmptyString("external_oauth_scope_mapping_attribute"), @@ -234,7 +234,7 @@ func ImportExternalOauthIntegration(ctx context.Context, d *schema.ResourceData, if prop, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "EXTERNAL_OAUTH_JWS_KEYS_URL" }); err == nil { - if err = d.Set("external_oauth_jws_keys_url", listValueToSlice(prop.Value, true)); err != nil { + if err = d.Set("external_oauth_jws_keys_url", listValueToSlice(prop.Value, false)); err != nil { return nil, err } } @@ -259,10 +259,36 @@ func ImportExternalOauthIntegration(ctx context.Context, d *schema.ResourceData, return nil, err } } + if prop, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "EXTERNAL_OAUTH_BLOCKED_ROLES_LIST" }); err == nil { - if err = d.Set("external_oauth_blocked_roles_list", listValueToSlice(prop.Value, false)); err != nil { + // handle blocked_roles_list + params, err := client.Parameters.ShowParameters(ctx, &sdk.ShowParametersOptions{ + Like: &sdk.Like{ + Pattern: sdk.Pointer("%%EXTERNAL_OAUTH%%"), + }, + In: &sdk.ParametersIn{ + Account: sdk.Pointer(true), + }, + }) + if err != nil { + return nil, err + } + var roles []string + found, err := collections.FindOne(params, func(p *sdk.Parameter) bool { return p.Key == "EXTERNAL_OAUTH_ADD_PRIVILEGED_ROLES_TO_BLOCKED_LIST" }) + if err == nil && (*found).Value == "true" { + unfilteredRoels := listValueToSlice(prop.Value, false) + for _, role := range unfilteredRoels { + if !slices.Contains([]string{"ACCOUNTADMIN", "SECURITYADMIN"}, role) { + roles = append(roles, role) + } + } + } else { + roles = listValueToSlice(prop.Value, false) + } + + if err = d.Set("external_oauth_blocked_roles_list", roles); err != nil { return nil, err } } @@ -276,7 +302,7 @@ func ImportExternalOauthIntegration(ctx context.Context, d *schema.ResourceData, if prop, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "EXTERNAL_OAUTH_AUDIENCE_LIST" }); err == nil { - if err = d.Set("external_oauth_audience_list", listValueToSlice(prop.Value, true)); err != nil { + if err = d.Set("external_oauth_audience_list", listValueToSlice(prop.Value, false)); err != nil { return nil, err } } @@ -509,7 +535,7 @@ func ReadContextExternalOauthIntegration(withExternalChangesMarking bool) schema } if err = handleExternalChangesToObjectInDescribe(d, describeMapping{"external_oauth_issuer", "external_oauth_issuer", externalOauthIssuer.Value, externalOauthIssuer.Value, nil}, - describeMapping{"external_oauth_jws_keys_url", "external_oauth_jws_keys_url", externalOauthJwsKeysUrl.Value, listValueToSlice(externalOauthJwsKeysUrl.Value, true), nil}, + describeMapping{"external_oauth_jws_keys_url", "external_oauth_jws_keys_url", externalOauthJwsKeysUrl.Value, listValueToSlice(externalOauthJwsKeysUrl.Value, false), nil}, describeMapping{"external_oauth_any_role_mode", "external_oauth_any_role_mode", externalOauthAnyRoleMode.Value, externalOauthAnyRoleMode.Value, nil}, describeMapping{"external_oauth_rsa_public_key", "external_oauth_rsa_public_key", externalOauthRsaPublicKey.Value, externalOauthRsaPublicKey.Value, nil}, describeMapping{"external_oauth_rsa_public_key_2", "external_oauth_rsa_public_key_2", externalOauthRsaPublicKey2.Value, externalOauthRsaPublicKey2.Value, nil}, @@ -599,23 +625,6 @@ func ReadContextExternalOauthIntegration(withExternalChangesMarking bool) schema if err = d.Set(ParametersAttributeName, []map[string]any{schemas.ExternalOauthParametersToSchema(params)}); err != nil { return diag.FromErr(err) } - // handle blocked_roles_list - found, err := collections.FindOne(params, func(p *sdk.Parameter) bool { return p.Key == "EXTERNAL_OAUTH_ADD_PRIVILEGED_ROLES_TO_BLOCKED_LIST" }) - if err == nil && (*found).Value == "true" { - externalOauthBlockedRolesList, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { - return property.Name == "EXTERNAL_OAUTH_BLOCKED_ROLES_LIST" - }) - x := d.Get("external_oauth_blocked_roles_list") - fmt.Println(x) - if err != nil { - return diag.FromErr(err) - } - if err := d.Set("external_oauth_blocked_roles_list", listValueToSlice(externalOauthBlockedRolesList.Value, false)); err != nil { - return diag.FromErr(err) - } - x = d.Get("external_oauth_blocked_roles_list") - fmt.Println(x) - } return nil } } @@ -635,10 +644,10 @@ func UpdateContextExternalOauthIntegration(ctx context.Context, d *schema.Resour } if d.HasChange("external_oauth_allowed_roles_list") { - v := d.Get("external_oauth_allowed_roles_list").([]any) + v := expandStringList(d.Get("external_oauth_allowed_roles_list").(*schema.Set).List()) allowedRoles := make([]sdk.AccountObjectIdentifier, len(v)) for i := range v { - allowedRoles[i] = sdk.NewAccountObjectIdentifier(v[i].(string)) + allowedRoles[i] = sdk.NewAccountObjectIdentifier(v[i]) } set.WithExternalOauthAllowedRolesList(sdk.AllowedRolesListRequest{AllowedRolesList: allowedRoles}) } @@ -658,11 +667,11 @@ func UpdateContextExternalOauthIntegration(ctx context.Context, d *schema.Resour } if d.HasChange("external_oauth_audience_list") { - v := d.Get("external_oauth_audience_list").([]any) + v := expandStringList(d.Get("external_oauth_audience_list").(*schema.Set).List()) if len(v) > 0 { audienceList := make([]sdk.AudienceListItem, len(v)) for i := range v { - audienceList[i] = sdk.AudienceListItem{Item: v[i].(string)} + audienceList[i] = sdk.AudienceListItem{Item: v[i]} } set.WithExternalOauthAudienceList(sdk.AudienceListRequest{AudienceList: audienceList}) } else { @@ -671,10 +680,11 @@ func UpdateContextExternalOauthIntegration(ctx context.Context, d *schema.Resour } if d.HasChange("external_oauth_blocked_roles_list") { - v := d.Get("external_oauth_blocked_roles_list").([]any) + vRaw := d.Get("external_oauth_blocked_roles_list") + v := expandStringList(vRaw.(*schema.Set).List()) blockedRoles := make([]sdk.AccountObjectIdentifier, len(v)) for i := range v { - blockedRoles[i] = sdk.NewAccountObjectIdentifier(v[i].(string)) + blockedRoles[i] = sdk.NewAccountObjectIdentifier(v[i]) } set.WithExternalOauthBlockedRolesList(sdk.BlockedRolesListRequest{BlockedRolesList: blockedRoles}) } @@ -685,11 +695,11 @@ func UpdateContextExternalOauthIntegration(ctx context.Context, d *schema.Resour } if d.HasChange("external_oauth_jws_keys_url") { - v := d.Get("external_oauth_jws_keys_url").([]any) + v := expandStringList(d.Get("external_oauth_jws_keys_url").(*schema.Set).List()) if len(v) > 0 { urls := make([]sdk.JwsKeysUrl, len(v)) for i := range v { - urls[i] = sdk.JwsKeysUrl{JwsKeyUrl: v[i].(string)} + urls[i] = sdk.JwsKeysUrl{JwsKeyUrl: v[i]} } set.WithExternalOauthJwsKeysUrl(urls) } @@ -720,10 +730,12 @@ func UpdateContextExternalOauthIntegration(ctx context.Context, d *schema.Resour } if d.HasChange("external_oauth_scope_mapping_attribute") { + // this field is required set.WithExternalOauthScopeMappingAttribute(d.Get("external_oauth_scope_mapping_attribute").(string)) } if d.HasChange("external_oauth_snowflake_user_mapping_attribute") { + // this field is required if v, ok := d.GetOk("external_oauth_snowflake_user_mapping_attribute"); ok { value, err := sdk.ToExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeOption(v.(string)) if err != nil { @@ -731,21 +743,21 @@ func UpdateContextExternalOauthIntegration(ctx context.Context, d *schema.Resour } set.WithExternalOauthSnowflakeUserMappingAttribute(value) } - // else: force new } if d.HasChange("external_oauth_token_user_mapping_claim") { - v := d.Get("external_oauth_token_user_mapping_claim").([]any) + v := expandStringList(d.Get("external_oauth_token_user_mapping_claim").(*schema.Set).List()) claims := make([]sdk.TokenUserMappingClaim, len(v)) for i := range v { claims[i] = sdk.TokenUserMappingClaim{ - Claim: v[i].(string), + Claim: v[i], } } set.WithExternalOauthTokenUserMappingClaim(claims) } if d.HasChange("external_oauth_type") { + // this field is required if v, ok := d.GetOk("external_oauth_type"); ok { value, err := sdk.ToExternalOauthSecurityIntegrationTypeOption(v.(string)) if err != nil { diff --git a/pkg/resources/external_oauth_integration_acceptance_test.go b/pkg/resources/external_oauth_integration_acceptance_test.go index 384fa8a69f..38f4e4f2ba 100644 --- a/pkg/resources/external_oauth_integration_acceptance_test.go +++ b/pkg/resources/external_oauth_integration_acceptance_test.go @@ -9,14 +9,272 @@ import ( acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/helpers" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/helpers/random" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/importchecks" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/planchecks" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/provider/resources" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + tfjson "github.com/hashicorp/terraform-json" "github.com/hashicorp/terraform-plugin-testing/config" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" "github.com/hashicorp/terraform-plugin-testing/tfversion" ) func TestAcc_ExternalOauthIntegration_basic(t *testing.T) { + id := acc.TestClient().Ids.RandomAccountObjectIdentifier() + role1, role1Cleanup := acc.TestClient().Role.CreateRole(t) + issuer := random.String() + t.Cleanup(role1Cleanup) + m := func(complete bool) map[string]config.Variable { + c := map[string]config.Variable{ + "enabled": config.BoolVariable(true), + "name": config.StringVariable(id.Name()), + "external_oauth_type": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), + "external_oauth_snowflake_user_mapping_attribute": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), + "external_oauth_token_user_mapping_claim": config.SetVariable(config.StringVariable("foo")), + "external_oauth_issuer": config.StringVariable(issuer), + "external_oauth_jws_keys_url": config.SetVariable(config.StringVariable("https://example.com")), + } + if complete { + c["external_oauth_add_privileged_roles_to_blocked_list"] = config.BoolVariable(true) + c["external_oauth_allowed_roles_list"] = config.SetVariable(config.StringVariable(role1.ID().Name())) + c["external_oauth_any_role_mode"] = config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)) + c["external_oauth_audience_list"] = config.SetVariable(config.StringVariable("foo")) + c["external_oauth_scope_delimiter"] = config.StringVariable(".") + c["external_oauth_scope_mapping_attribute"] = config.StringVariable("foo") + c["comment"] = config.StringVariable("foo") + } + return c + } + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: acc.CheckDestroy(t, resources.ExternalOauthSecurityIntegration), + Steps: []resource.TestStep{ + // create with empty optionals + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/basic"), + ConfigVariables: m(false), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "enabled", "true"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_issuer", issuer), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_jws_keys_url.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_jws_keys_url.0", "https://example.com"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_snowflake_user_mapping_attribute", string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_token_user_mapping_claim.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_token_user_mapping_claim.0", "foo"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "name", id.Name()), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_type", string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), + + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.name", id.Name()), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.integration_type", "EXTERNAL_OAUTH - CUSTOM"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.category", "SECURITY"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.enabled", "true"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.comment", ""), + resource.TestCheckResourceAttrSet("snowflake_external_oauth_integration.test", "show_output.0.created_on"), + + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.enabled.0.value", "true"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_issuer.0.value", issuer), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_jws_keys_url.0.value", "https://example.com"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_any_role_mode.0.value", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_allowed_roles_list.0.value", ""), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_audience_list.0.value", ""), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_token_user_mapping_claim.0.value", "['foo']"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_snowflake_user_mapping_attribute.0.value", string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_scope_delimiter.0.value", ","), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.comment.0.value", "")), + }, + // import - without optionals + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/basic"), + ConfigVariables: m(false), + ResourceName: "snowflake_external_oauth_integration.test", + ImportState: true, + ImportStateCheck: importchecks.ComposeImportStateCheck( + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "name", id.Name()), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "enabled", "true"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_issuer", issuer), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_type", string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_snowflake_user_mapping_attribute", string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_token_user_mapping_claim.#", "1"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_token_user_mapping_claim.0", "foo"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_jws_keys_url.#", "1"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_jws_keys_url.0", "https://example.com"), + ), + }, + // set optionals + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrlAndAllowedRolesList"), + ConfigVariables: m(true), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "comment", "foo"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "enabled", "true"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_allowed_roles_list.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_allowed_roles_list.0", role1.ID().Name()), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_any_role_mode", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_audience_list.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_audience_list.0", "foo"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_issuer", issuer), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_jws_keys_url.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_jws_keys_url.0", "https://example.com"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_scope_delimiter", "."), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_scope_mapping_attribute", "foo"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_snowflake_user_mapping_attribute", string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_token_user_mapping_claim.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_token_user_mapping_claim.0", "foo"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "name", id.Name()), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_type", string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), + + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.name", id.Name()), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.integration_type", "EXTERNAL_OAUTH - CUSTOM"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.category", "SECURITY"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.enabled", "true"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.comment", "foo"), + resource.TestCheckResourceAttrSet("snowflake_external_oauth_integration.test", "show_output.0.created_on"), + + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.enabled.0.value", "true"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_issuer.0.value", issuer), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_jws_keys_url.0.value", "https://example.com"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_any_role_mode.0.value", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_allowed_roles_list.0.value", role1.Name), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_audience_list.0.value", "foo"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_token_user_mapping_claim.0.value", "['foo']"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_snowflake_user_mapping_attribute.0.value", string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_scope_delimiter.0.value", "."), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.comment.0.value", "foo")), + }, + // import - complete + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrlAndAllowedRolesList"), + ConfigVariables: m(true), + ResourceName: "snowflake_external_oauth_integration.test", + ImportState: true, + ImportStateCheck: importchecks.ComposeImportStateCheck( + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "name", id.Name()), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "enabled", "true"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_issuer", issuer), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_type", string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_snowflake_user_mapping_attribute", string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_token_user_mapping_claim.#", "1"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_token_user_mapping_claim.0", "foo"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_jws_keys_url.#", "1"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_jws_keys_url.0", "https://example.com"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_any_role_mode", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_allowed_roles_list.#", "1"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_allowed_roles_list.0", role1.Name), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_audience_list.#", "1"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_audience_list.0", "foo"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_scope_delimiter", "."), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "comment", "foo"), + ), + }, + // change values externally + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrlAndAllowedRolesList"), + ConfigVariables: m(true), + PreConfig: func() { + acc.TestClient().SecurityIntegration.UpdateExternalOauth(t, sdk.NewAlterExternalOauthSecurityIntegrationRequest(id). + WithSet(*sdk.NewExternalOauthIntegrationSetRequest(). + WithExternalOauthSnowflakeUserMappingAttribute(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeLoginName), + )) + }, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + planchecks.ExpectDrift("snowflake_external_oauth_integration.test", "external_oauth_snowflake_user_mapping_attribute", sdk.String(string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), sdk.String(string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeLoginName))), + + planchecks.ExpectChange("snowflake_external_oauth_integration.test", "external_oauth_snowflake_user_mapping_attribute", tfjson.ActionUpdate, sdk.String(string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeLoginName)), sdk.String(string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress))), + }, + }, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "comment", "foo"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "enabled", "true"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_allowed_roles_list.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_allowed_roles_list.0", role1.ID().Name()), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_any_role_mode", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_audience_list.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_audience_list.0", "foo"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_issuer", issuer), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_jws_keys_url.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_jws_keys_url.0", "https://example.com"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_scope_delimiter", "."), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_scope_mapping_attribute", "foo"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_snowflake_user_mapping_attribute", string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_token_user_mapping_claim.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_token_user_mapping_claim.0", "foo"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "name", id.Name()), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_type", string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), + + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.name", id.Name()), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.integration_type", "EXTERNAL_OAUTH - CUSTOM"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.category", "SECURITY"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.enabled", "true"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.comment", "foo"), + resource.TestCheckResourceAttrSet("snowflake_external_oauth_integration.test", "show_output.0.created_on"), + + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.enabled.0.value", "true"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_issuer.0.value", issuer), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_jws_keys_url.0.value", "https://example.com"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_any_role_mode.0.value", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_allowed_roles_list.0.value", role1.Name), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_audience_list.0.value", "foo"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_token_user_mapping_claim.0.value", "['foo']"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_snowflake_user_mapping_attribute.0.value", string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_scope_delimiter.0.value", "."), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.comment.0.value", "foo")), + }, + // unset + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/basic"), + ConfigVariables: m(false), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "comment", ""), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "enabled", "true"), + resource.TestCheckNoResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_allowed_roles_list.#"), + resource.TestCheckNoResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_any_role_mode"), + resource.TestCheckNoResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_audience_list.#"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_issuer", issuer), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_jws_keys_url.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_jws_keys_url.0", "https://example.com"), + resource.TestCheckNoResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_scope_delimiter"), + resource.TestCheckNoResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_scope_mapping_attribute"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_snowflake_user_mapping_attribute", string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_token_user_mapping_claim.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_token_user_mapping_claim.0", "foo"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "name", id.Name()), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_type", string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), + + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.name", id.Name()), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.integration_type", "EXTERNAL_OAUTH - CUSTOM"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.category", "SECURITY"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.enabled", "true"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.comment", ""), + resource.TestCheckResourceAttrSet("snowflake_external_oauth_integration.test", "show_output.0.created_on"), + + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.enabled.0.value", "true"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_issuer.0.value", issuer), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_jws_keys_url.0.value", "https://example.com"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_any_role_mode.0.value", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_allowed_roles_list.0.value", ""), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_audience_list.0.value", ""), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_token_user_mapping_claim.0.value", "['foo']"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_snowflake_user_mapping_attribute.0.value", string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_scope_delimiter.0.value", ","), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.comment.0.value", "")), + }, + }, + }) } func TestAcc_ExternalOauthIntegration_completeWithJwsKeysUrlAndAllowedRolesList(t *testing.T) { @@ -39,8 +297,8 @@ func TestAcc_ExternalOauthIntegration_completeWithJwsKeysUrlAndAllowedRolesList( "external_oauth_scope_mapping_attribute": config.StringVariable("foo"), "external_oauth_snowflake_user_mapping_attribute": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), "external_oauth_token_user_mapping_claim": config.SetVariable(config.StringVariable("foo")), - "name": config.StringVariable(id.Name()), - "type": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), + "name": config.StringVariable(id.Name()), + "external_oauth_type": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), } } resource.Test(t, resource.TestCase{ @@ -71,6 +329,26 @@ func TestAcc_ExternalOauthIntegration_completeWithJwsKeysUrlAndAllowedRolesList( resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_token_user_mapping_claim.0", "foo"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "name", id.Name()), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_type", string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), + + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.name", id.Name()), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.integration_type", "EXTERNAL_OAUTH - CUSTOM"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.category", "SECURITY"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.enabled", "true"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.comment", "foo"), + resource.TestCheckResourceAttrSet("snowflake_external_oauth_integration.test", "show_output.0.created_on"), + + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.enabled.0.value", "true"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_issuer.0.value", issuer), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_jws_keys_url.0.value", "https://example.com"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_any_role_mode.0.value", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_allowed_roles_list.0.value", role1.Name), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_audience_list.0.value", "foo"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_token_user_mapping_claim.0.value", "['foo']"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_snowflake_user_mapping_attribute.0.value", string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_scope_delimiter.0.value", "."), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.comment.0.value", "foo"), ), }, { @@ -110,8 +388,8 @@ func TestAcc_ExternalOauthIntegration_completeWithRsaPublicKeysAndBlockedRolesLi "external_oauth_scope_mapping_attribute": config.StringVariable("foo"), "external_oauth_snowflake_user_mapping_attribute": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), "external_oauth_token_user_mapping_claim": config.SetVariable(config.StringVariable("foo")), - "name": config.StringVariable(id.Name()), - "type": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), + "name": config.StringVariable(id.Name()), + "external_oauth_type": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), } } resource.Test(t, resource.TestCase{ @@ -127,7 +405,7 @@ func TestAcc_ExternalOauthIntegration_completeWithRsaPublicKeysAndBlockedRolesLi Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "comment", "foo"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "enabled", "true"), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_blocked_roles_list.#", "3"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_blocked_roles_list.#", "1"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_blocked_roles_list.0", role1.ID().Name()), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_any_role_mode", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_audience_list.#", "1"), @@ -201,8 +479,8 @@ func TestAcc_ExternalOauthIntegration_completeWithRsaPublicKeysAndBlockedRolesLi "external_oauth_scope_mapping_attribute": config.StringVariable("foo"), "external_oauth_snowflake_user_mapping_attribute": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), "external_oauth_token_user_mapping_claim": config.SetVariable(config.StringVariable("foo")), - "name": config.StringVariable(id.Name()), - "type": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), + "name": config.StringVariable(id.Name()), + "external_oauth_type": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), } } resource.Test(t, resource.TestCase{ @@ -287,8 +565,8 @@ func TestAcc_ExternalOauthIntegration_invalid(t *testing.T) { "external_oauth_scope_mapping_attribute": config.StringVariable("foo"), "external_oauth_snowflake_user_mapping_attribute": config.StringVariable("invalid"), "external_oauth_token_user_mapping_claim": config.SetVariable(config.StringVariable("foo")), - "name": config.StringVariable("foo"), - "type": config.StringVariable("invalid"), + "name": config.StringVariable("foo"), + "external_oauth_type": config.StringVariable("invalid"), } } resource.Test(t, resource.TestCase{ diff --git a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/basic/test.tf b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/basic/test.tf index 761a6133b1..b0c709f4ca 100644 --- a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/basic/test.tf +++ b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/basic/test.tf @@ -1,8 +1,9 @@ resource "snowflake_external_oauth_integration" "test" { name = var.name - type = var.type + external_oauth_type = var.external_oauth_type enabled = var.enabled external_oauth_issuer = var.external_oauth_issuer - external_oauth_scope_mapping_attribute = var.external_oauth_scope_mapping_attribute + external_oauth_token_user_mapping_claim = var.external_oauth_token_user_mapping_claim external_oauth_snowflake_user_mapping_attribute = var.external_oauth_snowflake_user_mapping_attribute + external_oauth_jws_keys_url = var.external_oauth_jws_keys_url } diff --git a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/basic/variables.tf b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/basic/variables.tf index 459b631194..d1ddaa878a 100644 --- a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/basic/variables.tf +++ b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/basic/variables.tf @@ -1,7 +1,7 @@ variable "name" { type = string } -variable "type" { +variable "external_oauth_type" { type = string } variable "enabled" { @@ -14,5 +14,8 @@ variable "external_oauth_snowflake_user_mapping_attribute" { type = string } variable "external_oauth_token_user_mapping_claim" { - type = set(any) + type = set(string) +} +variable "external_oauth_jws_keys_url" { + type = set(string) } diff --git a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrlAndAllowedRolesList/test.tf b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrlAndAllowedRolesList/test.tf index 9583250522..955d08e6c3 100644 --- a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrlAndAllowedRolesList/test.tf +++ b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrlAndAllowedRolesList/test.tf @@ -11,5 +11,5 @@ resource "snowflake_external_oauth_integration" "test" { external_oauth_snowflake_user_mapping_attribute = var.external_oauth_snowflake_user_mapping_attribute external_oauth_token_user_mapping_claim = var.external_oauth_token_user_mapping_claim name = var.name - external_oauth_type = var.type + external_oauth_type = var.external_oauth_type } diff --git a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrlAndAllowedRolesList/variables.tf b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrlAndAllowedRolesList/variables.tf index a329605ff7..be2785222b 100644 --- a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrlAndAllowedRolesList/variables.tf +++ b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrlAndAllowedRolesList/variables.tf @@ -35,6 +35,6 @@ variable "external_oauth_token_user_mapping_claim" { variable "name" { type = string } -variable "type" { +variable "external_oauth_type" { type = string } diff --git a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithRsaPublicKeysAndBlockedRolesList/test.tf b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithRsaPublicKeysAndBlockedRolesList/test.tf index b695200823..20df3fe6dc 100644 --- a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithRsaPublicKeysAndBlockedRolesList/test.tf +++ b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithRsaPublicKeysAndBlockedRolesList/test.tf @@ -12,5 +12,5 @@ resource "snowflake_external_oauth_integration" "test" { external_oauth_snowflake_user_mapping_attribute = var.external_oauth_snowflake_user_mapping_attribute external_oauth_token_user_mapping_claim = var.external_oauth_token_user_mapping_claim name = var.name - external_oauth_type = var.type + external_oauth_type = var.external_oauth_type } diff --git a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithRsaPublicKeysAndBlockedRolesList/variables.tf b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithRsaPublicKeysAndBlockedRolesList/variables.tf index 8204a04da3..43a23ed6c7 100644 --- a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithRsaPublicKeysAndBlockedRolesList/variables.tf +++ b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/completeWithRsaPublicKeysAndBlockedRolesList/variables.tf @@ -31,7 +31,7 @@ variable "external_oauth_token_user_mapping_claim" { variable "name" { type = string } -variable "type" { +variable "external_oauth_type" { type = string } variable "external_oauth_rsa_public_key" { diff --git a/pkg/sdk/security_integrations_impl_gen.go b/pkg/sdk/security_integrations_impl_gen.go index 9ea4453311..5191b058f2 100644 --- a/pkg/sdk/security_integrations_impl_gen.go +++ b/pkg/sdk/security_integrations_impl_gen.go @@ -470,7 +470,6 @@ func (r *AlterExternalOauthSecurityIntegrationRequest) toOpts() *AlterExternalOa AudienceList: r.Set.ExternalOauthAudienceList.AudienceList, } } - } if r.Unset != nil { @@ -507,7 +506,6 @@ func (r *AlterOauthForPartnerApplicationsSecurityIntegrationRequest) toOpts() *A BlockedRolesList: r.Set.BlockedRolesList.BlockedRolesList, } } - } if r.Unset != nil { @@ -555,7 +553,6 @@ func (r *AlterOauthForCustomClientsSecurityIntegrationRequest) toOpts() *AlterOa BlockedRolesList: r.Set.BlockedRolesList.BlockedRolesList, } } - } if r.Unset != nil { From 2ead10055a2e7f03f38fe41259c3493660c9e974 Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Wed, 3 Jul 2024 15:25:14 +0200 Subject: [PATCH 06/14] Cleanup --- MIGRATION_GUIDE.md | 44 +++++++++++++++++-- pkg/resources/common.go | 28 ------------ pkg/resources/custom_diffs.go | 31 +------------ pkg/resources/external_oauth_integration.go | 16 +++++-- pkg/resources/helper_expansion.go | 14 ------ .../scim_integration_acceptance_test.go | 2 - v1-preparations/CHANGES_BEFORE_V1.md | 8 ++-- 7 files changed, 57 insertions(+), 86 deletions(-) diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index 2e72424d20..7b6b4aeb32 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -30,6 +30,42 @@ Added a new datasource enabling querying and filtering all types of security int The additional parameters call **DESC SECURITY INTEGRATION** (with `with_describe` turned on) **per security integration** returned by **SHOW SECURITY INTEGRATIONS**. It's important to limit the records and calls to Snowflake to the minimum. That's why we recommend assessing which information you need from the data source and then providing strong filters and turning off additional fields for better plan performance. +### snowflake_external_oauth_integration resource changes +#### *(behavior change)* Changed behavior of `sync_password` + +Now, the `sync_password` field will set the state value to `unknown` whenever the value is not set in the config. This indicates that the value on the Snowflake side is set to the Snowflake default. + +#### *(behavior change)* Renamed fields + +Renamed fields: +- `type` to `external_oauth_type` +- `issuer` to `external_oauth_issuer` +- `token_user_mapping_claims` to `external_oauth_token_user_mapping_claim` +- `snowflake_user_mapping_attribute` to `external_oauth_snowflake_user_mapping_attribute` +- `scope_mapping_attribute` to `external_oauth_scope_mapping_attribute` +- `jws_keys_urls` to `external_oauth_jws_keys_url` +- `rsa_public_key` to `external_oauth_rsa_public_key` +- `rsa_public_key_2` to `external_oauth_rsa_public_key_2` +- `blocked_roles` to `external_oauth_blocked_roles_list` +- `allowed_roles` to `external_oauth_allowed_roles_list` +- `audience_urls` to `external_oauth_audience_list` +- `any_role_mode` to `external_oauth_any_role_mode` +- `scope_delimiter` to `external_oauth_scope_delimiter` +to align with Snowflake docs. Please rename this field in your configuration files. State will be migrated automatically. + +#### *(feature)* New fields +Fields added to the resource: +- `enabled` +- `sync_password` +- `comment` + +#### *(behavior change)* Force new for multiple attributes after removing from config +Force new was added for the following attributes (because no usable SQL alter statements for them): +- `external_oauth_rsa_public_key` +- `external_oauth_rsa_public_key_2` +- `external_oauth_scope_mapping_attribute` +- `external_oauth_jws_keys_url` + ### snowflake_scim_integration resource changes #### *(behavior change)* Changed behavior of `sync_password` @@ -129,10 +165,10 @@ All the field changes in comparison to the previous database resource are: - removed: the field is removed from `snowflake_shared_database` as it doesn't have any effect on shared databases. - `from_database` - database cloning was entirely removed and is not possible by any of the new database resources. - `from_share` - the parameter was moved to the dedicated resource for databases created from shares `snowflake_shared_database`. Right now, it's a text field instead of a map. Additionally, instead of legacy account identifier format we're expecting the new one that with share looks like this: `..`. For more information on account identifiers, visit the [official documentation](https://docs.snowflake.com/en/user-guide/admin-account-identifier). -- p, +- p, - `from_replication` - the parameter was moved to the dedicated resource for databases created from primary databases `snowflake_secondary_database` - `replication_configuration` - renamed: was renamed to `configuration` and is only available in the `snowflake_database`. Its internal schema changed that instead of list of accounts, we expect a list of nested objects with accounts for which replication (and optionally failover) should be enabled. More information about converting between both versions [here](#resource-renamed-snowflake_database---snowflake_database_old). Additionally, instead of legacy account identifier format we're expecting the new one that looks like this: `.` (it will be automatically migrated to the recommended format by the state upgrader). For more information on account identifiers, visit the [official documentation](https://docs.snowflake.com/en/user-guide/admin-account-identifier). -- `data_retention_time_in_days` +- `data_retention_time_in_days` - in `snowflake_shared_database` - removed: the field is removed from `snowflake_shared_database` as it doesn't have any effect on shared databases. - in `snowflake_database` and `snowflake_secondary_database` @@ -213,7 +249,7 @@ The only difference would be that instead of writing/generating new configuratio - `pattern` was replaced by `like` field. - Additional filtering options added (`limit`). - Added missing fields returned by SHOW DATABASES and enclosed its output in `show_output` field. -- Added outputs from **DESC DATABASE** and **SHOW PARAMETERS IN DATABASE** (they can be turned off by declaring `with_describe = false` and `with_parameters = false`, **they're turned on by default**). +- Added outputs from **DESC DATABASE** and **SHOW PARAMETERS IN DATABASE** (they can be turned off by declaring `with_describe = false` and `with_parameters = false`, **they're turned on by default**). The additional parameters call **DESC DATABASE** (with `with_describe` turned on) and **SHOW PARAMETERS IN DATABASE** (with `with_parameters` turned on) **per database** returned by **SHOW DATABASES**. The outputs of both commands are held in `databases` entry, where **DESC DATABASE** is saved in the `describe_output` field, and **SHOW PARAMETERS IN DATABASE** in the `parameters` field. It's important to limit the records and calls to Snowflake to the minimum. That's why we recommend assessing which information you need from the data source and then providing strong filters and turning off additional fields for better plan performance. @@ -237,7 +273,7 @@ resource "snowflake_tag_masking_policy_association" "name" { masking_policy_id = snowflake_masking_policy.example_masking_policy.id } ``` - + After ```terraform resource "snowflake_tag_masking_policy_association" "name" { diff --git a/pkg/resources/common.go b/pkg/resources/common.go index 2591a4bcf6..6f4731bbc4 100644 --- a/pkg/resources/common.go +++ b/pkg/resources/common.go @@ -1,12 +1,6 @@ package resources import ( - "crypto/sha256" - "crypto/x509" - "encoding/base64" - "encoding/pem" - "errors" - "fmt" "strings" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" @@ -60,28 +54,6 @@ func suppressQuoting(_, oldValue, newValue string, _ *schema.ResourceData) bool } } -func RSAKeyHash(key string) (string, error) { - keyBytes := []byte(fmt.Sprintf("-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----", key)) - - block, _ := pem.Decode(keyBytes) - if block == nil || block.Type != "PUBLIC KEY" { - return "", errors.New("Failed to decode PEM block containing public key") - } - - pubKey, err := x509.ParsePKIXPublicKey(block.Bytes) - if err != nil { - return "", fmt.Errorf("Unable to parse public key: %w", err) - } - - pubKeyBytes, err := x509.MarshalPKIXPublicKey(pubKey) - if err != nil { - return "", fmt.Errorf("Unable to marshal public key: %w", err) - } - - hash := sha256.Sum256(pubKeyBytes) - return fmt.Sprintf("SHA256:%s", base64.StdEncoding.EncodeToString(hash[:])), nil -} - func listValueToSlice(value string, trimQuotes bool) []string { value = strings.TrimLeft(value, "[") value = strings.TrimRight(value, "]") diff --git a/pkg/resources/custom_diffs.go b/pkg/resources/custom_diffs.go index d6876432dc..103d56e620 100644 --- a/pkg/resources/custom_diffs.go +++ b/pkg/resources/custom_diffs.go @@ -2,14 +2,11 @@ package resources import ( "context" - "fmt" "log" "strconv" "strings" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" @@ -85,39 +82,13 @@ func ForceNewIfChangeToEmptyString(key string) schema.CustomizeDiffFunc { } // ForceNewIfChangeToEmptySet sets a ForceNew for a set field which was set to an empty value. -func ForceNewIfChangeToEmptySet[T any](key string) schema.CustomizeDiffFunc { +func ForceNewIfChangeToEmptySet(key string) schema.CustomizeDiffFunc { return customdiff.ForceNewIfChange(key, func(ctx context.Context, oldValue, newValue, meta any) bool { oldList, newList := oldValue.(*schema.Set).List(), newValue.(*schema.Set).List() return len(oldList) > 0 && len(newList) == 0 }) } -func ModifyStateIfParameterSet(key, param string, modify func(*schema.ResourceDiff) error) schema.CustomizeDiffFunc { - return func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error { - client := meta.(*provider.Context).Client - params, err := client.Parameters.ShowParameters(ctx, &sdk.ShowParametersOptions{ - Like: &sdk.Like{ - Pattern: sdk.Pointer(param), - }, - In: &sdk.ParametersIn{ - Account: sdk.Pointer(true), - }, - }) - if err != nil { - return err - } - found, err := collections.FindOne(params, func(p *sdk.Parameter) bool { return p.Key == param }) - if err != nil { - return fmt.Errorf("parameter %s not found", param) - } - param := helpers.StringToBool((*found).Value) - if !param { - return nil - } - return modify(d) - } -} - type parameter struct { parameterName sdk.AccountParameter valueType valueType diff --git a/pkg/resources/external_oauth_integration.go b/pkg/resources/external_oauth_integration.go index 1a1abc136d..95871c6273 100644 --- a/pkg/resources/external_oauth_integration.go +++ b/pkg/resources/external_oauth_integration.go @@ -187,7 +187,7 @@ func ExternalOauthIntegration() *schema.Resource { ForceNewIfChangeToEmptyString("external_oauth_rsa_public_key"), ForceNewIfChangeToEmptyString("external_oauth_rsa_public_key_2"), ForceNewIfChangeToEmptyString("external_oauth_scope_mapping_attribute"), - ForceNewIfChangeToEmptySet[any]("external_oauth_jws_keys_url"), + ForceNewIfChangeToEmptySet("external_oauth_jws_keys_url"), ComputedIfAnyAttributeChanged(ShowOutputAttributeName, "enabled", "external_oauth_type", "comment"), ComputedIfAnyAttributeChanged(DescribeOutputAttributeName, "enabled", "external_oauth_issuer", "external_oauth_jws_keys_url", "external_oauth_any_role_mode", "external_oauth_rsa_public_key", "external_oauth_rsa_public_key_2", "external_oauth_blocked_roles_list", "external_oauth_allowed_roles_list", @@ -363,7 +363,12 @@ func CreateContextExternalOauthIntegration(ctx context.Context, d *schema.Resour } if v, ok := d.GetOk("external_oauth_allowed_roles_list"); ok { - req.WithExternalOauthAllowedRolesList(sdk.AllowedRolesListRequest{AllowedRolesList: expandObjectIdentifierList(v.(*schema.Set).List())}) + vList := expandStringList(v.(*schema.Set).List()) + allowedRoles := make([]sdk.AccountObjectIdentifier, len(vList)) + for i := range vList { + allowedRoles[i] = sdk.NewAccountObjectIdentifier(vList[i]) + } + req.WithExternalOauthAllowedRolesList(sdk.AllowedRolesListRequest{AllowedRolesList: allowedRoles}) } if v, ok := d.GetOk("external_oauth_any_role_mode"); ok { @@ -385,9 +390,12 @@ func CreateContextExternalOauthIntegration(ctx context.Context, d *schema.Resour } if v, ok := d.GetOk("external_oauth_blocked_roles_list"); ok { - if _, okAllowed := d.GetOk("external_oauth_allowed_roles_list"); !okAllowed { - req.WithExternalOauthBlockedRolesList(sdk.BlockedRolesListRequest{BlockedRolesList: expandObjectIdentifierList(v.(*schema.Set).List())}) + vList := expandStringList(v.(*schema.Set).List()) + blockedRoles := make([]sdk.AccountObjectIdentifier, len(vList)) + for i := range vList { + blockedRoles[i] = sdk.NewAccountObjectIdentifier(vList[i]) } + req.WithExternalOauthBlockedRolesList(sdk.BlockedRolesListRequest{BlockedRolesList: blockedRoles}) } if v, ok := d.GetOk("external_oauth_jws_keys_url"); ok { diff --git a/pkg/resources/helper_expansion.go b/pkg/resources/helper_expansion.go index 41e40b7b5d..82a00efcad 100644 --- a/pkg/resources/helper_expansion.go +++ b/pkg/resources/helper_expansion.go @@ -2,8 +2,6 @@ package resources import ( "slices" - - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" ) // borrowed from https://github.com/terraform-providers/terraform-provider-aws/blob/master/aws/structure.go#L924:6 @@ -29,18 +27,6 @@ func expandStringList(configured []interface{}) []string { return vs } -// TODO: unit tests, maybe do with transform func -func expandObjectIdentifierList(configured []interface{}) []sdk.AccountObjectIdentifier { - vs := make([]sdk.AccountObjectIdentifier, 0, len(configured)) - for _, v := range configured { - val, ok := v.(string) - if ok && val != "" { - vs = append(vs, sdk.NewAccountObjectIdentifier(val)) - } - } - return vs -} - func expandStringListAllowEmpty(configured []interface{}) []string { // Allow empty values during expansion vs := make([]string, 0, len(configured)) diff --git a/pkg/resources/scim_integration_acceptance_test.go b/pkg/resources/scim_integration_acceptance_test.go index f95e696e92..aa93f03a8a 100644 --- a/pkg/resources/scim_integration_acceptance_test.go +++ b/pkg/resources/scim_integration_acceptance_test.go @@ -292,8 +292,6 @@ func TestAcc_ScimIntegration_migrateFromVersion091(t *testing.T) { { ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, Config: scimIntegrationv092(id.Name(), role.Name()), - // TODO: Remove (?) - ExpectNonEmptyPlan: true, ConfigPlanChecks: resource.ConfigPlanChecks{ PreApply: []plancheck.PlanCheck{ planchecks.ExpectChange("snowflake_scim_integration.test", "name", tfjson.ActionUpdate, sdk.String(id.Name()), sdk.String(id.Name())), diff --git a/v1-preparations/CHANGES_BEFORE_V1.md b/v1-preparations/CHANGES_BEFORE_V1.md index 3ec832d168..bc661c4f10 100644 --- a/v1-preparations/CHANGES_BEFORE_V1.md +++ b/v1-preparations/CHANGES_BEFORE_V1.md @@ -4,14 +4,14 @@ This document is a supplement to all the resource changes described in the [migr ## Default values For any resource that went through the rework as part of the [resource preparation for V1](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/ROADMAP.md#preparing-essential-ga-objects-for-the-provider-v1), -the behaviour for default values may change from the previous one. +the behaviour for default values may change from the previous one. -In the past, the provider copied defaults from Snowflake, creating a tight coupling between them. +In the past, the provider copied defaults from Snowflake, creating a tight coupling between them. However, this approach posed a challenge as the defaults on the Snowflake side could change and vary between accounts based on their configurations. Now, whenever the value is not specified in the configuration, we let the Snowflake fill out the default value for a given field -(if there is one). Using such defaults may lead to non-idempotent cases where the same configuration may -create a resource with slightly different configuration in Snowflake (depending on the Snowflake Edition and Version, +(if there is one). Using such defaults may lead to non-idempotent cases where the same configuration may +create a resource with slightly different configuration in Snowflake (depending on the Snowflake Edition and Version, current account configuration, and most-likely other factors). That is why we recommend setting optional fields where you want to ensure that the specified value has been set on the Snowflake side. From 5ff48da066c60c93f99b35f3e9e0a285b6972ba2 Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Thu, 4 Jul 2024 08:18:15 +0200 Subject: [PATCH 07/14] Add state upgraders and update migration guide --- MIGRATION_GUIDE.md | 10 - pkg/resources/external_oauth_integration.go | 30 ++- ...ernal_oauth_integration_acceptance_test.go | 219 ++++++++++++++++++ ...ernal_oauth_integration_stage_upgraders.go | 38 +++ 4 files changed, 279 insertions(+), 18 deletions(-) create mode 100644 pkg/resources/external_oauth_integration_stage_upgraders.go diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index 7b6b4aeb32..a52c3c897d 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -31,12 +31,8 @@ Added a new datasource enabling querying and filtering all types of security int It's important to limit the records and calls to Snowflake to the minimum. That's why we recommend assessing which information you need from the data source and then providing strong filters and turning off additional fields for better plan performance. ### snowflake_external_oauth_integration resource changes -#### *(behavior change)* Changed behavior of `sync_password` - -Now, the `sync_password` field will set the state value to `unknown` whenever the value is not set in the config. This indicates that the value on the Snowflake side is set to the Snowflake default. #### *(behavior change)* Renamed fields - Renamed fields: - `type` to `external_oauth_type` - `issuer` to `external_oauth_issuer` @@ -53,12 +49,6 @@ Renamed fields: - `scope_delimiter` to `external_oauth_scope_delimiter` to align with Snowflake docs. Please rename this field in your configuration files. State will be migrated automatically. -#### *(feature)* New fields -Fields added to the resource: -- `enabled` -- `sync_password` -- `comment` - #### *(behavior change)* Force new for multiple attributes after removing from config Force new was added for the following attributes (because no usable SQL alter statements for them): - `external_oauth_rsa_public_key` diff --git a/pkg/resources/external_oauth_integration.go b/pkg/resources/external_oauth_integration.go index 95871c6273..432a2956d4 100644 --- a/pkg/resources/external_oauth_integration.go +++ b/pkg/resources/external_oauth_integration.go @@ -15,6 +15,7 @@ import ( "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/schemas" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -77,16 +78,18 @@ var oauthExternalIntegrationSchema = map[string]*schema.Schema{ Description: "Specifies the endpoint or a list of endpoints from which to download public keys or certificates to validate an External OAuth access token. The maximum number of URLs that can be specified in the list is 3.", }, "external_oauth_rsa_public_key": { - Type: schema.TypeString, - Optional: true, - ConflictsWith: []string{"external_oauth_jws_keys_url"}, - Description: "Specifies a Base64-encoded RSA public key, without the -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY----- headers.", + Type: schema.TypeString, + Optional: true, + Description: "Specifies a Base64-encoded RSA public key, without the -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY----- headers.", + DiffSuppressFunc: ignoreTrimSpaceSuppressFunc, + ConflictsWith: []string{"external_oauth_jws_keys_url"}, }, "external_oauth_rsa_public_key_2": { - Type: schema.TypeString, - Optional: true, - Description: "Specifies a second RSA public key, without the -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY----- headers. Used for key rotation.", - ConflictsWith: []string{"external_oauth_jws_keys_url"}, + Type: schema.TypeString, + Optional: true, + Description: "Specifies a second RSA public key, without the -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY----- headers. Used for key rotation.", + DiffSuppressFunc: ignoreTrimSpaceSuppressFunc, + ConflictsWith: []string{"external_oauth_jws_keys_url"}, }, "external_oauth_blocked_roles_list": { Type: schema.TypeSet, @@ -178,6 +181,8 @@ var oauthExternalIntegrationSchema = map[string]*schema.Schema{ func ExternalOauthIntegration() *schema.Resource { return &schema.Resource{ + SchemaVersion: 1, + CreateContext: CreateContextExternalOauthIntegration, ReadContext: ReadContextExternalOauthIntegration(true), UpdateContext: UpdateContextExternalOauthIntegration, @@ -197,6 +202,15 @@ func ExternalOauthIntegration() *schema.Resource { Importer: &schema.ResourceImporter{ StateContext: ImportExternalOauthIntegration, }, + + StateUpgraders: []schema.StateUpgrader{ + { + Version: 0, + // setting type to cty.EmptyObject is a bit hacky here but following https://developer.hashicorp.com/terraform/plugin/framework/migrating/resources/state-upgrade#sdkv2-1 would require lots of repetitive code; this should work with cty.EmptyObject + Type: cty.EmptyObject, + Upgrade: v092ExternalOauthIntegrationStateUpgrader, + }, + }, } } diff --git a/pkg/resources/external_oauth_integration_acceptance_test.go b/pkg/resources/external_oauth_integration_acceptance_test.go index 38f4e4f2ba..8e5d4d55e5 100644 --- a/pkg/resources/external_oauth_integration_acceptance_test.go +++ b/pkg/resources/external_oauth_integration_acceptance_test.go @@ -618,3 +618,222 @@ func TestAcc_ExternalOauthIntegration_InvalidIncomplete(t *testing.T) { }, }) } + +func TestAcc_ExternalOauthIntegration_migrateFromVersion092_withRsaPublicKeysAndBlockedRolesList(t *testing.T) { + id := acc.TestClient().Ids.RandomAccountObjectIdentifier() + role1, role1Cleanup := acc.TestClient().Role.CreateRole(t) + t.Cleanup(role1Cleanup) + issuer := random.String() + rsaKey := random.GenerateRSAPublicKey(t) + resourceName := "snowflake_external_oauth_integration.test" + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + + Steps: []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{ + "snowflake": { + VersionConstraint: "=0.92.0", + Source: "Snowflake-Labs/snowflake", + }, + }, + Config: externalOauthIntegrationWithRsaPublicKeysAndBlockedRolesListv092(id.Name(), issuer, rsaKey, role1.Name), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", id.Name()), + resource.TestCheckResourceAttr(resourceName, "type", string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), + resource.TestCheckResourceAttr(resourceName, "issuer", issuer), + resource.TestCheckResourceAttr(resourceName, "token_user_mapping_claims.#", "1"), + resource.TestCheckResourceAttr(resourceName, "token_user_mapping_claims.0", "foo"), + resource.TestCheckResourceAttr(resourceName, "snowflake_user_mapping_attribute", string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeLoginName)), + resource.TestCheckResourceAttr(resourceName, "scope_mapping_attribute", "foo"), + resource.TestCheckResourceAttr(resourceName, "rsa_public_key", rsaKey), + resource.TestCheckResourceAttr(resourceName, "rsa_public_key_2", rsaKey), + resource.TestCheckResourceAttr(resourceName, "blocked_roles.#", "1"), + resource.TestCheckResourceAttr(resourceName, "blocked_roles.0", role1.Name), + resource.TestCheckResourceAttr(resourceName, "audience_urls.#", "1"), + resource.TestCheckResourceAttr(resourceName, "audience_urls.0", "foo"), + resource.TestCheckResourceAttr(resourceName, "any_role_mode", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), + resource.TestCheckResourceAttr(resourceName, "scope_delimiter", ":"), + ), + }, + { + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + Config: externalOauthIntegrationWithRsaPublicKeysAndBlockedRolesListv093(id.Name(), issuer, rsaKey, role1.Name), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", id.Name()), + resource.TestCheckResourceAttr(resourceName, "external_oauth_type", string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), + resource.TestCheckResourceAttr(resourceName, "external_oauth_issuer", issuer), + resource.TestCheckResourceAttr(resourceName, "external_oauth_token_user_mapping_claim.#", "1"), + resource.TestCheckResourceAttr(resourceName, "external_oauth_token_user_mapping_claim.0", "foo"), + resource.TestCheckResourceAttr(resourceName, "external_oauth_snowflake_user_mapping_attribute", string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeLoginName)), + resource.TestCheckResourceAttr(resourceName, "external_oauth_scope_mapping_attribute", "foo"), + resource.TestCheckResourceAttr(resourceName, "external_oauth_rsa_public_key", rsaKey), + resource.TestCheckResourceAttr(resourceName, "external_oauth_rsa_public_key_2", rsaKey), + resource.TestCheckResourceAttr(resourceName, "external_oauth_blocked_roles_list.#", "1"), + resource.TestCheckResourceAttr(resourceName, "external_oauth_blocked_roles_list.0", role1.Name), + resource.TestCheckResourceAttr(resourceName, "external_oauth_audience_list.#", "1"), + resource.TestCheckResourceAttr(resourceName, "external_oauth_audience_list.0", "foo"), + resource.TestCheckResourceAttr(resourceName, "external_oauth_any_role_mode", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), + resource.TestCheckResourceAttr(resourceName, "external_oauth_scope_delimiter", ":"), + ), + }, + }, + }) +} + +func externalOauthIntegrationWithRsaPublicKeysAndBlockedRolesListv092(name, issuer, rsaKey, roleName string) string { + s := ` +locals { + key_raw = <<-EOT +%s + EOT + key = trimsuffix(local.key_raw, "\n") +} +resource "snowflake_external_oauth_integration" "test" { + name = "%s" + enabled = true + type = "CUSTOM" + issuer = "%s" + token_user_mapping_claims = ["foo"] + snowflake_user_mapping_attribute = "LOGIN_NAME" + scope_mapping_attribute = "foo" + rsa_public_key = local.key + rsa_public_key_2 = local.key + blocked_roles = ["%s"] + audience_urls = ["foo"] + any_role_mode = "DISABLE" + scope_delimiter = ":" +}` + return fmt.Sprintf(s, rsaKey, name, issuer, roleName) +} + +func externalOauthIntegrationWithRsaPublicKeysAndBlockedRolesListv093(name, issuer, rsaKey, roleName string) string { + s := ` +locals { + key_raw = <<-EOT +%s + EOT + key = trimsuffix(local.key_raw, "\n") +} +resource "snowflake_external_oauth_integration" "test" { + name = "%s" + enabled = true + external_oauth_type = "CUSTOM" + external_oauth_issuer = "%s" + external_oauth_token_user_mapping_claim = ["foo"] + external_oauth_snowflake_user_mapping_attribute = "LOGIN_NAME" + external_oauth_scope_mapping_attribute = "foo" + external_oauth_rsa_public_key = local.key + external_oauth_rsa_public_key_2 = local.key + external_oauth_blocked_roles_list = ["%s"] + external_oauth_audience_list = ["foo"] + external_oauth_any_role_mode = "DISABLE" + external_oauth_scope_delimiter = ":" +}` + return fmt.Sprintf(s, rsaKey, name, issuer, roleName) +} + +func TestAcc_ExternalOauthIntegration_migrateFromVersion092_withJwsKeysUrlAndAllowedRolesList(t *testing.T) { + id := acc.TestClient().Ids.RandomAccountObjectIdentifier() + role1, role1Cleanup := acc.TestClient().Role.CreateRole(t) + t.Cleanup(role1Cleanup) + issuer := random.String() + resourceName := "snowflake_external_oauth_integration.test" + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + + Steps: []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{ + "snowflake": { + VersionConstraint: "=0.92.0", + Source: "Snowflake-Labs/snowflake", + }, + }, + Config: externalOauthIntegrationWithJwsKeysUrlAndAllowedRolesListv092(id.Name(), issuer, role1.Name), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", id.Name()), + resource.TestCheckResourceAttr(resourceName, "type", string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), + resource.TestCheckResourceAttr(resourceName, "issuer", issuer), + resource.TestCheckResourceAttr(resourceName, "token_user_mapping_claims.#", "1"), + resource.TestCheckResourceAttr(resourceName, "token_user_mapping_claims.0", "foo"), + resource.TestCheckResourceAttr(resourceName, "snowflake_user_mapping_attribute", string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeLoginName)), + resource.TestCheckResourceAttr(resourceName, "scope_mapping_attribute", "foo"), + resource.TestCheckResourceAttr(resourceName, "jws_keys_urls.#", "1"), + resource.TestCheckResourceAttr(resourceName, "jws_keys_urls.0", "https://example.com"), + resource.TestCheckResourceAttr(resourceName, "allowed_roles.#", "1"), + resource.TestCheckResourceAttr(resourceName, "allowed_roles.0", role1.Name), + resource.TestCheckResourceAttr(resourceName, "audience_urls.#", "1"), + resource.TestCheckResourceAttr(resourceName, "audience_urls.0", "foo"), + resource.TestCheckResourceAttr(resourceName, "any_role_mode", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), + resource.TestCheckResourceAttr(resourceName, "scope_delimiter", ":"), + ), + }, + { + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + Config: externalOauthIntegrationWithJwsKeysUrlAndAllowedRolesListv093(id.Name(), issuer, role1.Name), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", id.Name()), + resource.TestCheckResourceAttr(resourceName, "external_oauth_type", string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), + resource.TestCheckResourceAttr(resourceName, "external_oauth_issuer", issuer), + resource.TestCheckResourceAttr(resourceName, "external_oauth_token_user_mapping_claim.#", "1"), + resource.TestCheckResourceAttr(resourceName, "external_oauth_token_user_mapping_claim.0", "foo"), + resource.TestCheckResourceAttr(resourceName, "external_oauth_snowflake_user_mapping_attribute", string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeLoginName)), + resource.TestCheckResourceAttr(resourceName, "external_oauth_scope_mapping_attribute", "foo"), + resource.TestCheckResourceAttr(resourceName, "external_oauth_jws_keys_url.#", "1"), + resource.TestCheckResourceAttr(resourceName, "external_oauth_jws_keys_url.0", "https://example.com"), + resource.TestCheckResourceAttr(resourceName, "external_oauth_allowed_roles_list.#", "1"), + resource.TestCheckResourceAttr(resourceName, "external_oauth_allowed_roles_list.0", role1.Name), + resource.TestCheckResourceAttr(resourceName, "external_oauth_audience_list.#", "1"), + resource.TestCheckResourceAttr(resourceName, "external_oauth_audience_list.0", "foo"), + resource.TestCheckResourceAttr(resourceName, "external_oauth_any_role_mode", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), + resource.TestCheckResourceAttr(resourceName, "external_oauth_scope_delimiter", ":"), + ), + }, + }, + }) +} + +func externalOauthIntegrationWithJwsKeysUrlAndAllowedRolesListv092(name, issuer, roleName string) string { + s := ` +resource "snowflake_external_oauth_integration" "test" { + name = "%s" + enabled = true + type = "CUSTOM" + issuer = "%s" + token_user_mapping_claims = ["foo"] + snowflake_user_mapping_attribute = "LOGIN_NAME" + scope_mapping_attribute = "foo" + jws_keys_urls = ["https://example.com"] + allowed_roles = ["%s"] + audience_urls = ["foo"] + any_role_mode = "DISABLE" + scope_delimiter = ":" +}` + return fmt.Sprintf(s, name, issuer, roleName) +} + +func externalOauthIntegrationWithJwsKeysUrlAndAllowedRolesListv093(name, issuer, roleName string) string { + s := ` +resource "snowflake_external_oauth_integration" "test" { + name = "%s" + enabled = true + external_oauth_type = "CUSTOM" + external_oauth_issuer = "%s" + external_oauth_token_user_mapping_claim = ["foo"] + external_oauth_snowflake_user_mapping_attribute = "LOGIN_NAME" + external_oauth_scope_mapping_attribute = "foo" + external_oauth_jws_keys_url = ["https://example.com"] + external_oauth_allowed_roles_list = ["%s"] + external_oauth_audience_list = ["foo"] + external_oauth_any_role_mode = "DISABLE" + external_oauth_scope_delimiter = ":" +}` + return fmt.Sprintf(s, name, issuer, roleName) +} diff --git a/pkg/resources/external_oauth_integration_stage_upgraders.go b/pkg/resources/external_oauth_integration_stage_upgraders.go new file mode 100644 index 0000000000..88ba0dd65a --- /dev/null +++ b/pkg/resources/external_oauth_integration_stage_upgraders.go @@ -0,0 +1,38 @@ +package resources + +import ( + "context" +) + +func v092ExternalOauthIntegrationStateUpgrader(ctx context.Context, rawState map[string]any, meta any) (map[string]any, error) { + if rawState == nil { + return rawState, nil + } + + type renameField struct { + from string + to string + } + fieldsToRename := []renameField{ + {from: "type", to: "external_oauth_type"}, + {from: "issuer", to: "external_oauth_issuer"}, + {from: "token_user_mapping_claims", to: "external_oauth_token_user_mapping_claim"}, + {from: "snowflake_user_mapping_attribute", to: "external_oauth_snowflake_user_mapping_attribute"}, + {from: "scope_mapping_attribute", to: "external_oauth_scope_mapping_attribute"}, + {from: "jws_keys_urls", to: "external_oauth_jws_keys_url"}, + {from: "rsa_public_key", to: "external_oauth_rsa_public_key"}, + {from: "rsa_public_key_2", to: "external_oauth_rsa_public_key_2"}, + {from: "blocked_roles", to: "external_oauth_blocked_roles_list"}, + {from: "allowed_roles", to: "external_oauth_allowed_roles_list"}, + {from: "audience_urls", to: "external_oauth_audience_list"}, + {from: "any_role_mode", to: "external_oauth_any_role_mode"}, + {from: "scope_delimiter", to: "external_oauth_scope_delimiter"}, + } + + for _, field := range fieldsToRename { + rawState[field.to] = rawState[field.from] + delete(rawState, field.from) + } + + return rawState, nil +} From 32a0c0bb4d3981afb8a777d9a61fb81f042caaa3 Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Fri, 5 Jul 2024 08:10:12 +0200 Subject: [PATCH 08/14] Suggestions from review --- MIGRATION_GUIDE.md | 9 +- pkg/resources/common.go | 17 -- pkg/resources/external_oauth_integration.go | 164 +++++------------- ...ernal_oauth_integration_acceptance_test.go | 98 +++++++++-- pkg/resources/saml2_integration.go | 8 +- pkg/resources/show_and_describe_handlers.go | 11 +- pkg/sdk/parsers.go | 5 +- pkg/sdk/parsers_test.go | 9 +- 8 files changed, 165 insertions(+), 156 deletions(-) diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index 090729c627..2029058167 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -50,11 +50,18 @@ Renamed fields: to align with Snowflake docs. Please rename this field in your configuration files. State will be migrated automatically. #### *(behavior change)* Force new for multiple attributes after removing from config -Force new was added for the following attributes (because no usable SQL alter statements for them): +Conditional force new was added for the following attributes when they are removed from config. There are no alter statements supporting UNSET on these fields. - `external_oauth_rsa_public_key` - `external_oauth_rsa_public_key_2` - `external_oauth_scope_mapping_attribute` - `external_oauth_jws_keys_url` +- `external_oauth_token_user_mapping_claim` + +#### *(behavior change)* Changed diff suppress for some fields +The fields listed below had diff suppress which removed '-' from strings. Now, this behavior is removed, so if you had '-' in these strings, please remove them. Note that '-' in these values is not allowed by Snowflake. +- `external_oauth_snowflake_user_mapping_attribute` +- `external_oauth_type` +- `external_oauth_any_role_mode` ### snowflake_scim_integration resource changes #### *(behavior change)* Changed behavior of `sync_password` diff --git a/pkg/resources/common.go b/pkg/resources/common.go index 6f4731bbc4..a2a33a7c1d 100644 --- a/pkg/resources/common.go +++ b/pkg/resources/common.go @@ -54,23 +54,6 @@ func suppressQuoting(_, oldValue, newValue string, _ *schema.ResourceData) bool } } -func listValueToSlice(value string, trimQuotes bool) []string { - value = strings.TrimLeft(value, "[") - value = strings.TrimRight(value, "]") - if value == "" { - return nil - } - elems := strings.Split(value, ",") - for i := range elems { - if trimQuotes { - elems[i] = strings.Trim(elems[i], " '") - } else { - elems[i] = strings.Trim(elems[i], " ") - } - } - return elems -} - func ctyValToSliceString(valueElems []cty.Value) []string { elems := make([]string, len(valueElems)) for i, v := range valueElems { diff --git a/pkg/resources/external_oauth_integration.go b/pkg/resources/external_oauth_integration.go index 432a2956d4..5129329546 100644 --- a/pkg/resources/external_oauth_integration.go +++ b/pkg/resources/external_oauth_integration.go @@ -6,7 +6,6 @@ import ( "fmt" "reflect" "slices" - "strings" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" @@ -19,7 +18,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) var oauthExternalIntegrationSchema = map[string]*schema.Schema{ @@ -30,16 +28,11 @@ var oauthExternalIntegrationSchema = map[string]*schema.Schema{ Description: "Specifies the name of the External Oath integration. This name follows the rules for Object Identifiers. The name should be unique among security integrations in your account.", }, "external_oauth_type": { - Type: schema.TypeString, - Required: true, - Description: fmt.Sprintf("Specifies the OAuth 2.0 authorization server to be Okta, Microsoft Azure AD, Ping Identity PingFederate, or a Custom OAuth 2.0 authorization server. Valid options are: %v", sdk.AllExternalOauthSecurityIntegrationTypes), - ValidateFunc: validation.StringInSlice(sdk.AsStringList(sdk.AllExternalOauthSecurityIntegrationTypes), true), - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - normalize := func(s string) string { - return strings.ToUpper(strings.ReplaceAll(s, "-", "")) - } - return normalize(old) == normalize(new) - }, + Type: schema.TypeString, + Required: true, + Description: fmt.Sprintf("Specifies the OAuth 2.0 authorization server to be Okta, Microsoft Azure AD, Ping Identity PingFederate, or a Custom OAuth 2.0 authorization server. Valid options are: %v", sdk.AllExternalOauthSecurityIntegrationTypes), + ValidateDiagFunc: sdkValidation(sdk.ToExternalOauthSecurityIntegrationTypeOption), + DiffSuppressFunc: NormalizeAndCompare(sdk.ToExternalOauthSecurityIntegrationTypeOption), }, "enabled": { Type: schema.TypeBool, @@ -58,16 +51,11 @@ var oauthExternalIntegrationSchema = map[string]*schema.Schema{ Description: "Specifies the access token claim or claims that can be used to map the access token to a Snowflake user record.", }, "external_oauth_snowflake_user_mapping_attribute": { - Type: schema.TypeString, - Required: true, - Description: fmt.Sprintf("Indicates which Snowflake user record attribute should be used to map the access token to a Snowflake user record. Valid options are: %v", sdk.AllExternalOauthSecurityIntegrationSnowflakeUserMappingAttributes), - ValidateFunc: validation.StringInSlice(sdk.AsStringList(sdk.AllExternalOauthSecurityIntegrationSnowflakeUserMappingAttributes), true), - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - normalize := func(s string) string { - return strings.ToUpper(strings.ReplaceAll(s, "-", "")) - } - return normalize(old) == normalize(new) - }, + Type: schema.TypeString, + Required: true, + Description: fmt.Sprintf("Indicates which Snowflake user record attribute should be used to map the access token to a Snowflake user record. Valid options are: %v", sdk.AllExternalOauthSecurityIntegrationSnowflakeUserMappingAttributes), + ValidateDiagFunc: sdkValidation(sdk.ToExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeOption), + DiffSuppressFunc: NormalizeAndCompare(sdk.ToExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeOption), }, "external_oauth_jws_keys_url": { Type: schema.TypeSet, @@ -109,7 +97,7 @@ var oauthExternalIntegrationSchema = map[string]*schema.Schema{ return false } - return old == "ACCOUNTADMIN" || old == "SECURITYADMIN" + return slices.Contains([]string{"ACCOUNTADMIN", "SECURITYADMIN"}, old) }, ConflictsWith: []string{"external_oauth_allowed_roles_list"}, }, @@ -127,16 +115,11 @@ var oauthExternalIntegrationSchema = map[string]*schema.Schema{ Description: "Specifies additional values that can be used for the access token's audience validation on top of using the Customer's Snowflake Account URL ", }, "external_oauth_any_role_mode": { - Type: schema.TypeString, - Optional: true, - Description: fmt.Sprintf("Specifies whether the OAuth client or user can use a role that is not defined in the OAuth access token. Valid options are: %v", sdk.AsStringList(sdk.AllExternalOauthSecurityIntegrationAnyRoleModes)), - ValidateFunc: validation.StringInSlice(sdk.AsStringList(sdk.AllExternalOauthSecurityIntegrationAnyRoleModes), true), - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - normalize := func(s string) string { - return strings.ToUpper(strings.ReplaceAll(s, "-", "")) - } - return normalize(old) == normalize(new) - }, + Type: schema.TypeString, + Optional: true, + Description: fmt.Sprintf("Specifies whether the OAuth client or user can use a role that is not defined in the OAuth access token. Valid options are: %v", sdk.AsStringList(sdk.AllExternalOauthSecurityIntegrationAnyRoleModes)), + ValidateDiagFunc: sdkValidation(sdk.ToExternalOauthSecurityIntegrationAnyRoleModeOption), + DiffSuppressFunc: NormalizeAndCompare(sdk.ToExternalOauthSecurityIntegrationAnyRoleModeOption), }, "external_oauth_scope_delimiter": { Type: schema.TypeString, @@ -169,7 +152,7 @@ var oauthExternalIntegrationSchema = map[string]*schema.Schema{ Schema: schemas.DescribeExternalOauthSecurityIntegrationSchema, }, }, - ParametersAttributeName: { + RelatedParametersAttributeName: { Type: schema.TypeList, Computed: true, Description: "Paramteres related to this security integration.", @@ -193,6 +176,7 @@ func ExternalOauthIntegration() *schema.Resource { ForceNewIfChangeToEmptyString("external_oauth_rsa_public_key_2"), ForceNewIfChangeToEmptyString("external_oauth_scope_mapping_attribute"), ForceNewIfChangeToEmptySet("external_oauth_jws_keys_url"), + ForceNewIfChangeToEmptySet("external_oauth_token_user_mapping_claim"), ComputedIfAnyAttributeChanged(ShowOutputAttributeName, "enabled", "external_oauth_type", "comment"), ComputedIfAnyAttributeChanged(DescribeOutputAttributeName, "enabled", "external_oauth_issuer", "external_oauth_jws_keys_url", "external_oauth_any_role_mode", "external_oauth_rsa_public_key", "external_oauth_rsa_public_key_2", "external_oauth_blocked_roles_list", "external_oauth_allowed_roles_list", @@ -248,7 +232,7 @@ func ImportExternalOauthIntegration(ctx context.Context, d *schema.ResourceData, if prop, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "EXTERNAL_OAUTH_JWS_KEYS_URL" }); err == nil { - if err = d.Set("external_oauth_jws_keys_url", listValueToSlice(prop.Value, false)); err != nil { + if err = d.Set("external_oauth_jws_keys_url", sdk.ParseCommaSeparatedStringArray(prop.Value, false)); err != nil { return nil, err } } @@ -278,28 +262,21 @@ func ImportExternalOauthIntegration(ctx context.Context, d *schema.ResourceData, return property.Name == "EXTERNAL_OAUTH_BLOCKED_ROLES_LIST" }); err == nil { // handle blocked_roles_list - params, err := client.Parameters.ShowParameters(ctx, &sdk.ShowParametersOptions{ - Like: &sdk.Like{ - Pattern: sdk.Pointer("%%EXTERNAL_OAUTH%%"), - }, - In: &sdk.ParametersIn{ - Account: sdk.Pointer(true), - }, - }) + found, err := client.Parameters.ShowAccountParameter(ctx, sdk.AccountParameterExternalOAuthAddPrivilegedRolesToBlockedList) if err != nil { return nil, err } + var roles []string - found, err := collections.FindOne(params, func(p *sdk.Parameter) bool { return p.Key == "EXTERNAL_OAUTH_ADD_PRIVILEGED_ROLES_TO_BLOCKED_LIST" }) if err == nil && (*found).Value == "true" { - unfilteredRoels := listValueToSlice(prop.Value, false) - for _, role := range unfilteredRoels { + unfilteredRoles := sdk.ParseCommaSeparatedStringArray(prop.Value, false) + for _, role := range unfilteredRoles { if !slices.Contains([]string{"ACCOUNTADMIN", "SECURITYADMIN"}, role) { roles = append(roles, role) } } } else { - roles = listValueToSlice(prop.Value, false) + roles = sdk.ParseCommaSeparatedStringArray(prop.Value, false) } if err = d.Set("external_oauth_blocked_roles_list", roles); err != nil { @@ -309,21 +286,21 @@ func ImportExternalOauthIntegration(ctx context.Context, d *schema.ResourceData, if prop, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "EXTERNAL_OAUTH_ALLOWED_ROLES_LIST" }); err == nil { - if err = d.Set("external_oauth_allowed_roles_list", listValueToSlice(prop.Value, false)); err != nil { + if err = d.Set("external_oauth_allowed_roles_list", sdk.ParseCommaSeparatedStringArray(prop.Value, false)); err != nil { return nil, err } } if prop, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "EXTERNAL_OAUTH_AUDIENCE_LIST" }); err == nil { - if err = d.Set("external_oauth_audience_list", listValueToSlice(prop.Value, false)); err != nil { + if err = d.Set("external_oauth_audience_list", sdk.ParseCommaSeparatedStringArray(prop.Value, false)); err != nil { return nil, err } } if prop, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "EXTERNAL_OAUTH_TOKEN_USER_MAPPING_CLAIM" }); err == nil { - if err = d.Set("external_oauth_token_user_mapping_claim", listValueToSlice(prop.Value, true)); err != nil { + if err = d.Set("external_oauth_token_user_mapping_claim", sdk.ParseCommaSeparatedStringArray(prop.Value, true)); err != nil { return nil, err } } @@ -557,14 +534,14 @@ func ReadContextExternalOauthIntegration(withExternalChangesMarking bool) schema } if err = handleExternalChangesToObjectInDescribe(d, describeMapping{"external_oauth_issuer", "external_oauth_issuer", externalOauthIssuer.Value, externalOauthIssuer.Value, nil}, - describeMapping{"external_oauth_jws_keys_url", "external_oauth_jws_keys_url", externalOauthJwsKeysUrl.Value, listValueToSlice(externalOauthJwsKeysUrl.Value, false), nil}, + describeMapping{"external_oauth_jws_keys_url", "external_oauth_jws_keys_url", externalOauthJwsKeysUrl.Value, sdk.ParseCommaSeparatedStringArray(externalOauthJwsKeysUrl.Value, false), nil}, describeMapping{"external_oauth_any_role_mode", "external_oauth_any_role_mode", externalOauthAnyRoleMode.Value, externalOauthAnyRoleMode.Value, nil}, describeMapping{"external_oauth_rsa_public_key", "external_oauth_rsa_public_key", externalOauthRsaPublicKey.Value, externalOauthRsaPublicKey.Value, nil}, describeMapping{"external_oauth_rsa_public_key_2", "external_oauth_rsa_public_key_2", externalOauthRsaPublicKey2.Value, externalOauthRsaPublicKey2.Value, nil}, - describeMapping{"external_oauth_blocked_roles_list", "external_oauth_blocked_roles_list", externalOauthBlockedRolesList.Value, listValueToSlice(externalOauthBlockedRolesList.Value, false), nil}, - describeMapping{"external_oauth_allowed_roles_list", "external_oauth_allowed_roles_list", externalOauthAllowedRolesList.Value, listValueToSlice(externalOauthAllowedRolesList.Value, false), nil}, - describeMapping{"external_oauth_audience_list", "external_oauth_audience_list", externalOauthAudienceList.Value, listValueToSlice(externalOauthAudienceList.Value, false), nil}, - describeMapping{"external_oauth_token_user_mapping_claim", "external_oauth_token_user_mapping_claim", externalOauthTokenUserMappingClaim.Value, listValueToSlice(externalOauthTokenUserMappingClaim.Value, true), nil}, + describeMapping{"external_oauth_blocked_roles_list", "external_oauth_blocked_roles_list", externalOauthBlockedRolesList.Value, sdk.ParseCommaSeparatedStringArray(externalOauthBlockedRolesList.Value, false), nil}, + describeMapping{"external_oauth_allowed_roles_list", "external_oauth_allowed_roles_list", externalOauthAllowedRolesList.Value, sdk.ParseCommaSeparatedStringArray(externalOauthAllowedRolesList.Value, false), nil}, + describeMapping{"external_oauth_audience_list", "external_oauth_audience_list", externalOauthAudienceList.Value, sdk.ParseCommaSeparatedStringArray(externalOauthAudienceList.Value, false), nil}, + describeMapping{"external_oauth_token_user_mapping_claim", "external_oauth_token_user_mapping_claim", externalOauthTokenUserMappingClaim.Value, sdk.ParseCommaSeparatedStringArray(externalOauthTokenUserMappingClaim.Value, true), nil}, describeMapping{"external_oauth_snowflake_user_mapping_attribute", "external_oauth_snowflake_user_mapping_attribute", externalOauthSnowflakeUserMappingAttribute.Value, externalOauthSnowflakeUserMappingAttribute.Value, nil}, describeMapping{"external_oauth_scope_delimiter", "external_oauth_scope_delimiter", externalOauthScopeDelimiter.Value, externalOauthScopeDelimiter.Value, nil}, ); err != nil { @@ -572,59 +549,20 @@ func ReadContextExternalOauthIntegration(withExternalChangesMarking bool) schema } } - if !d.GetRawConfig().IsNull() { - if v := d.GetRawConfig().AsValueMap()["external_oauth_jws_keys_url"]; !v.IsNull() { - if err = d.Set("external_oauth_jws_keys_url", ctyValToSliceString(v.AsValueSlice())); err != nil { - return diag.FromErr(err) - } - } - if v := d.GetRawConfig().AsValueMap()["external_oauth_rsa_public_key"]; !v.IsNull() { - if err = d.Set("external_oauth_rsa_public_key", v.AsString()); err != nil { - return diag.FromErr(err) - } - } - if v := d.GetRawConfig().AsValueMap()["external_oauth_rsa_public_key_2"]; !v.IsNull() { - if err = d.Set("external_oauth_rsa_public_key_2", v.AsString()); err != nil { - return diag.FromErr(err) - } - } - if v := d.GetRawConfig().AsValueMap()["external_oauth_blocked_roles_list"]; !v.IsNull() { - if err = d.Set("external_oauth_blocked_roles_list", ctyValToSliceString(v.AsValueSlice())); err != nil { - return diag.FromErr(err) - } - } - if v := d.GetRawConfig().AsValueMap()["external_oauth_allowed_roles_list"]; !v.IsNull() { - if err = d.Set("external_oauth_allowed_roles_list", ctyValToSliceString(v.AsValueSlice())); err != nil { - return diag.FromErr(err) - } - } - if v := d.GetRawConfig().AsValueMap()["external_oauth_audience_list"]; !v.IsNull() { - if err = d.Set("external_oauth_audience_list", ctyValToSliceString(v.AsValueSlice())); err != nil { - return diag.FromErr(err) - } - } - if v := d.GetRawConfig().AsValueMap()["external_oauth_any_role_mode"]; !v.IsNull() { - if err = d.Set("external_oauth_any_role_mode", v.AsString()); err != nil { - return diag.FromErr(err) - } - } - if v := d.GetRawConfig().AsValueMap()["external_oauth_scope_delimiter"]; !v.IsNull() { - if err = d.Set("external_oauth_scope_delimiter", v.AsString()); err != nil { - return diag.FromErr(err) - } - } - if v := d.GetRawConfig().AsValueMap()["external_oauth_scope_mapping_attribute"]; !v.IsNull() { - if err = d.Set("external_oauth_scope_mapping_attribute", v.AsString()); err != nil { - return diag.FromErr(err) - } - } - if v := d.GetRawConfig().AsValueMap()["comment"]; !v.IsNull() { - if err = d.Set("comment", v.AsString()); err != nil { - return diag.FromErr(err) - } - } + if err = setStateToValuesFromConfig(d, warehouseSchema, []string{ + "external_oauth_jws_keys_url", + "external_oauth_rsa_public_key", + "external_oauth_rsa_public_key_2", + "external_oauth_blocked_roles_list", + "external_oauth_allowed_roles_list", + "external_oauth_audience_list", + "external_oauth_any_role_mode", + "external_oauth_scope_delimiter", + "external_oauth_scope_mapping_attribute", + "comment", + }); err != nil { + return diag.FromErr(err) } - if err = d.Set(ShowOutputAttributeName, []map[string]any{schemas.SecurityIntegrationToSchema(integration)}); err != nil { return diag.FromErr(err) } @@ -633,18 +571,11 @@ func ReadContextExternalOauthIntegration(withExternalChangesMarking bool) schema return diag.FromErr(err) } - params, err := client.Parameters.ShowParameters(ctx, &sdk.ShowParametersOptions{ - Like: &sdk.Like{ - Pattern: sdk.Pointer("%%EXTERNAL_OAUTH%%"), - }, - In: &sdk.ParametersIn{ - Account: sdk.Pointer(true), - }, - }) + param, err := client.Parameters.ShowAccountParameter(ctx, sdk.AccountParameterExternalOAuthAddPrivilegedRolesToBlockedList) if err != nil { return diag.FromErr(err) } - if err = d.Set(ParametersAttributeName, []map[string]any{schemas.ExternalOauthParametersToSchema(params)}); err != nil { + if err = d.Set(RelatedParametersAttributeName, []map[string]any{schemas.ExternalOauthParametersToSchema([]*sdk.Parameter{param})}); err != nil { return diag.FromErr(err) } return nil @@ -725,6 +656,7 @@ func UpdateContextExternalOauthIntegration(ctx context.Context, d *schema.Resour } set.WithExternalOauthJwsKeysUrl(urls) } + // else: force new } if d.HasChange("external_oauth_rsa_public_key") { diff --git a/pkg/resources/external_oauth_integration_acceptance_test.go b/pkg/resources/external_oauth_integration_acceptance_test.go index 8e5d4d55e5..6e9b2719c5 100644 --- a/pkg/resources/external_oauth_integration_acceptance_test.go +++ b/pkg/resources/external_oauth_integration_acceptance_test.go @@ -2,6 +2,7 @@ package resources_test import ( "fmt" + "regexp" "sort" "strings" "testing" @@ -188,8 +189,8 @@ func TestAcc_ExternalOauthIntegration_basic(t *testing.T) { }, ConfigPlanChecks: resource.ConfigPlanChecks{ PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction("snowflake_external_oauth_integration.test", plancheck.ResourceActionUpdate), planchecks.ExpectDrift("snowflake_external_oauth_integration.test", "external_oauth_snowflake_user_mapping_attribute", sdk.String(string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), sdk.String(string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeLoginName))), - planchecks.ExpectChange("snowflake_external_oauth_integration.test", "external_oauth_snowflake_user_mapping_attribute", tfjson.ActionUpdate, sdk.String(string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeLoginName)), sdk.String(string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress))), }, }, @@ -236,6 +237,11 @@ func TestAcc_ExternalOauthIntegration_basic(t *testing.T) { { ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/basic"), ConfigVariables: m(false), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction("snowflake_external_oauth_integration.test", plancheck.ResourceActionUpdate), + }, + }, Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "comment", ""), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "enabled", "true"), @@ -370,7 +376,7 @@ func TestAcc_ExternalOauthIntegration_completeWithRsaPublicKeysAndBlockedRolesLi expectedRoles := []string{"ACCOUNTADMIN", "SECURITYADMIN", role1.Name} sort.Strings(expectedRoles) issuer := random.String() - rsaKey := random.GenerateRSAPublicKey(t) + rsaKey, _ := random.GenerateRSAPublicKey(t) paramCleanup := acc.TestClient().Parameter.UpdateAccountParameterTemporarily(t, sdk.AccountParameterExternalOAuthAddPrivilegedRolesToBlockedList, "true") t.Cleanup(paramCleanup) m := func() map[string]config.Variable { @@ -461,7 +467,7 @@ func TestAcc_ExternalOauthIntegration_completeWithRsaPublicKeysAndBlockedRolesLi role1, role1Cleanup := acc.TestClient().Role.CreateRole(t) t.Cleanup(role1Cleanup) issuer := random.String() - rsaKey := random.GenerateRSAPublicKey(t) + rsaKey, _ := random.GenerateRSAPublicKey(t) paramCleanup := acc.TestClient().Parameter.UpdateAccountParameterTemporarily(t, sdk.AccountParameterExternalOAuthAddPrivilegedRolesToBlockedList, "false") t.Cleanup(paramCleanup) m := func() map[string]config.Variable { @@ -547,7 +553,83 @@ func TestAcc_ExternalOauthIntegration_completeWithRsaPublicKeysAndBlockedRolesLi }) } -func TestAcc_ExternalOauthIntegration_invalid(t *testing.T) { +func TestAcc_ExternalOauthIntegration_invalidAnyRoleMode(t *testing.T) { + m := func() map[string]config.Variable { + return map[string]config.Variable{ + "comment": config.StringVariable("foo"), + "enabled": config.BoolVariable(true), + "external_oauth_add_privileged_roles_to_blocked_list": config.BoolVariable(true), + "external_oauth_allowed_roles_list": config.SetVariable(config.StringVariable("foo")), + "external_oauth_any_role_mode": config.StringVariable("invalid"), + "external_oauth_audience_list": config.SetVariable(config.StringVariable("foo")), + "external_oauth_blocked_roles_list": config.SetVariable(config.StringVariable("foo")), + "external_oauth_issuer": config.StringVariable("foo"), + "external_oauth_jws_keys_url": config.SetVariable(config.StringVariable("foo")), + "external_oauth_rsa_public_key": config.StringVariable("foo"), + "external_oauth_rsa_public_key_2": config.StringVariable("foo"), + "external_oauth_scope_delimiter": config.StringVariable("foo"), + "external_oauth_scope_mapping_attribute": config.StringVariable("foo"), + "external_oauth_snowflake_user_mapping_attribute": config.StringVariable("invalid"), + "external_oauth_token_user_mapping_claim": config.SetVariable(config.StringVariable("foo")), + "name": config.StringVariable("foo"), + "external_oauth_type": config.StringVariable("invalid"), + } + } + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrlAndAllowedRolesList"), + ConfigVariables: m(), + ExpectError: regexp.MustCompile(fmt.Sprintf("expected external_oauth_any_role_mode to be one of %q, got invalid", sdk.AsStringList(sdk.AllExternalOauthSecurityIntegrationAnyRoleModes))), + }, + }, + }) +} + +func TestAcc_ExternalOauthIntegration_invalidSnowflakeUserMappingAttribute(t *testing.T) { + m := func() map[string]config.Variable { + return map[string]config.Variable{ + "comment": config.StringVariable("foo"), + "enabled": config.BoolVariable(true), + "external_oauth_add_privileged_roles_to_blocked_list": config.BoolVariable(true), + "external_oauth_allowed_roles_list": config.SetVariable(config.StringVariable("foo")), + "external_oauth_any_role_mode": config.StringVariable("invalid"), + "external_oauth_audience_list": config.SetVariable(config.StringVariable("foo")), + "external_oauth_blocked_roles_list": config.SetVariable(config.StringVariable("foo")), + "external_oauth_issuer": config.StringVariable("foo"), + "external_oauth_jws_keys_url": config.SetVariable(config.StringVariable("foo")), + "external_oauth_rsa_public_key": config.StringVariable("foo"), + "external_oauth_rsa_public_key_2": config.StringVariable("foo"), + "external_oauth_scope_delimiter": config.StringVariable("foo"), + "external_oauth_scope_mapping_attribute": config.StringVariable("foo"), + "external_oauth_snowflake_user_mapping_attribute": config.StringVariable("invalid"), + "external_oauth_token_user_mapping_claim": config.SetVariable(config.StringVariable("foo")), + "name": config.StringVariable("foo"), + "external_oauth_type": config.StringVariable("invalid"), + } + } + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrlAndAllowedRolesList"), + ConfigVariables: m(), + ExpectError: regexp.MustCompile(fmt.Sprintf("expected external_oauth_snowflake_user_mapping_attribute to be one of %q, got invalid", sdk.AsStringList(sdk.AllExternalOauthSecurityIntegrationSnowflakeUserMappingAttributes))), + }, + }, + }) +} + +func TestAcc_ExternalOauthIntegration_invalidOauthType(t *testing.T) { m := func() map[string]config.Variable { return map[string]config.Variable{ "comment": config.StringVariable("foo"), @@ -579,11 +661,7 @@ func TestAcc_ExternalOauthIntegration_invalid(t *testing.T) { { ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrlAndAllowedRolesList"), ConfigVariables: m(), - ExpectError: helpers.MatchAllStringsInOrderNonOverlapping([]string{ - fmt.Sprintf("expected external_oauth_any_role_mode to be one of %q, got invalid", sdk.AsStringList(sdk.AllExternalOauthSecurityIntegrationAnyRoleModes)), - fmt.Sprintf("expected external_oauth_snowflake_user_mapping_attribute to be one of %q, got invalid", sdk.AsStringList(sdk.AllExternalOauthSecurityIntegrationSnowflakeUserMappingAttributes)), - fmt.Sprintf("expected external_oauth_type to be one of %q, got invalid", sdk.AsStringList(sdk.AllExternalOauthSecurityIntegrationTypes)), - }), + ExpectError: regexp.MustCompile(fmt.Sprintf("expected external_oauth_type to be one of %q, got invalid", sdk.AsStringList(sdk.AllOauthSecurityIntegrationClientTypes))), }, }, }) @@ -624,7 +702,7 @@ func TestAcc_ExternalOauthIntegration_migrateFromVersion092_withRsaPublicKeysAnd role1, role1Cleanup := acc.TestClient().Role.CreateRole(t) t.Cleanup(role1Cleanup) issuer := random.String() - rsaKey := random.GenerateRSAPublicKey(t) + rsaKey, _ := random.GenerateRSAPublicKey(t) resourceName := "snowflake_external_oauth_integration.test" resource.Test(t, resource.TestCase{ PreCheck: func() { acc.TestAccPreCheck(t) }, diff --git a/pkg/resources/saml2_integration.go b/pkg/resources/saml2_integration.go index 259c8847df..6b03ef3c75 100644 --- a/pkg/resources/saml2_integration.go +++ b/pkg/resources/saml2_integration.go @@ -320,7 +320,7 @@ func ImportSaml2Integration(ctx context.Context, d *schema.ResourceData, meta an if err != nil { return nil, fmt.Errorf("failed to find allowed user domains, err = %w", err) } - if err := d.Set("allowed_user_domains", sdk.ParseCommaSeparatedStringArray(allowedUserDomains.Value)); err != nil { + if err := d.Set("allowed_user_domains", sdk.ParseCommaSeparatedStringArray(allowedUserDomains.Value, false)); err != nil { return nil, err } @@ -330,7 +330,7 @@ func ImportSaml2Integration(ctx context.Context, d *schema.ResourceData, meta an if err != nil { return nil, fmt.Errorf("failed to find allowed email patterns, err = %w", err) } - if err := d.Set("allowed_email_patterns", sdk.ParseCommaSeparatedStringArray(allowedEmailDomains.Value)); err != nil { + if err := d.Set("allowed_email_patterns", sdk.ParseCommaSeparatedStringArray(allowedEmailDomains.Value, false)); err != nil { return nil, err } @@ -525,7 +525,7 @@ func ReadContextSAML2Integration(withExternalChangesMarking bool) schema.ReadCon if err != nil { return diag.FromErr(fmt.Errorf("failed to find allowed user domains, err = %w", err)) } - if err := d.Set("allowed_user_domains", sdk.ParseCommaSeparatedStringArray(allowedUserDomains.Value)); err != nil { + if err := d.Set("allowed_user_domains", sdk.ParseCommaSeparatedStringArray(allowedUserDomains.Value, false)); err != nil { return diag.FromErr(err) } @@ -535,7 +535,7 @@ func ReadContextSAML2Integration(withExternalChangesMarking bool) schema.ReadCon if err != nil { return diag.FromErr(fmt.Errorf("failed to find allowed email patterns, err = %w", err)) } - if err := d.Set("allowed_email_patterns", sdk.ParseCommaSeparatedStringArray(allowedEmailDomains.Value)); err != nil { + if err := d.Set("allowed_email_patterns", sdk.ParseCommaSeparatedStringArray(allowedEmailDomains.Value, false)); err != nil { return diag.FromErr(err) } diff --git a/pkg/resources/show_and_describe_handlers.go b/pkg/resources/show_and_describe_handlers.go index 91a0fc9cb1..fcf546ee69 100644 --- a/pkg/resources/show_and_describe_handlers.go +++ b/pkg/resources/show_and_describe_handlers.go @@ -7,9 +7,10 @@ import ( ) const ( - ShowOutputAttributeName = "show_output" - DescribeOutputAttributeName = "describe_output" - ParametersAttributeName = "parameters" + ShowOutputAttributeName = "show_output" + DescribeOutputAttributeName = "describe_output" + ParametersAttributeName = "parameters" + RelatedParametersAttributeName = "related_parameters" ) // handleExternalChangesToObjectInShow assumes that show output is kept in ShowOutputAttributeName attribute @@ -106,6 +107,10 @@ func setStateToValuesFromConfig(d *schema.ResourceData, resourceSchema map[strin if err := d.Set(field, v.AsString()); err != nil { return err } + case schema.TypeSet: + if err := d.Set(field, ctyValToSliceString(v.AsValueSlice())); err != nil { + return err + } default: log.Printf("[DEBUG] field %s has unsupported schema type %v not found", field, schemaField.Type) } diff --git a/pkg/sdk/parsers.go b/pkg/sdk/parsers.go index 8e415373f3..5ca1d1ae82 100644 --- a/pkg/sdk/parsers.go +++ b/pkg/sdk/parsers.go @@ -22,7 +22,7 @@ func ParseTimestampWithOffset(s string, dateTimeFormat string) (string, error) { // 1. The list is enclosed by [] brackets, and they shouldn't be a part of any item's value // 2. Items are separated by commas, and they shouldn't be a part of any item's value // 3. Items can have as many spaces in between, but after separation they will be trimmed and shouldn't be a part of any item's value -func ParseCommaSeparatedStringArray(value string) []string { +func ParseCommaSeparatedStringArray(value string, trimQuotes bool) []string { if strings.HasPrefix(value, "[") && strings.HasSuffix(value, "]") { if value == "[]" { return make([]string, 0) @@ -32,6 +32,9 @@ func ParseCommaSeparatedStringArray(value string) []string { trimmedListItems := make([]string, len(listItems)) for i, item := range listItems { trimmedListItems[i] = strings.TrimSpace(item) + if trimQuotes { + trimmedListItems[i] = strings.Trim(trimmedListItems[i], "'") + } } return trimmedListItems } diff --git a/pkg/sdk/parsers_test.go b/pkg/sdk/parsers_test.go index 6ff3a59c16..c82a3e50a3 100644 --- a/pkg/sdk/parsers_test.go +++ b/pkg/sdk/parsers_test.go @@ -8,9 +8,10 @@ import ( func TestParseCommaSeparatedStringArray(t *testing.T) { testCases := []struct { - Name string - Value string - Result []string + Name string + Value string + TrimQuotes bool + Result []string }{ { Name: "empty list", @@ -51,7 +52,7 @@ func TestParseCommaSeparatedStringArray(t *testing.T) { for _, tc := range testCases { t.Run(tc.Name, func(t *testing.T) { - assert.Equal(t, tc.Result, ParseCommaSeparatedStringArray(tc.Value)) + assert.Equal(t, tc.Result, ParseCommaSeparatedStringArray(tc.Value, tc.TrimQuotes)) }) } } From b7f5ef2adf1db4359fc5b3be59541ab46cc6b995 Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Fri, 5 Jul 2024 08:32:26 +0200 Subject: [PATCH 09/14] Review comments 2 --- MIGRATION_GUIDE.md | 6 ++ docs/resources/external_oauth_integration.md | 34 ++++----- pkg/resources/custom_diffs.go | 16 ----- pkg/resources/external_oauth_integration.go | 25 +++---- ...ernal_oauth_integration_acceptance_test.go | 70 +++++++++---------- 5 files changed, 71 insertions(+), 80 deletions(-) diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index 2029058167..157ed1fc0b 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -57,6 +57,12 @@ Conditional force new was added for the following attributes when they are remov - `external_oauth_jws_keys_url` - `external_oauth_token_user_mapping_claim` +#### *(behavior change)* Conflicting fields +Fields listed below can not be set at the same time in Snowflake. They are marked as conflicting fields. +- `external_oauth_jws_keys_url` <-> `external_oauth_rsa_public_key` +- `external_oauth_jws_keys_url` <-> `external_oauth_rsa_public_key_2` +- `external_oauth_allowed_roles_list` <-> `external_oauth_blocked_roles_list` + #### *(behavior change)* Changed diff suppress for some fields The fields listed below had diff suppress which removed '-' from strings. Now, this behavior is removed, so if you had '-' in these strings, please remove them. Note that '-' in these values is not allowed by Snowflake. - `external_oauth_snowflake_user_mapping_attribute` diff --git a/docs/resources/external_oauth_integration.md b/docs/resources/external_oauth_integration.md index 1e387b44e7..7ef9b1ac27 100644 --- a/docs/resources/external_oauth_integration.md +++ b/docs/resources/external_oauth_integration.md @@ -2,12 +2,12 @@ page_title: "snowflake_external_oauth_integration Resource - terraform-provider-snowflake" subcategory: "" description: |- - + Resource used to manage external oauth security integrations. For more information, check documentation https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-oauth-external. --- # snowflake_external_oauth_integration (Resource) - +Resource used to manage external oauth security integrations. For more information, check [documentation](https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-oauth-external). ## Example Usage @@ -63,29 +63,29 @@ resource "snowflake_external_oauth_integration" "test" { - `enabled` (Boolean) Specifies whether to initiate operation of the integration or suspend it. - `external_oauth_issuer` (String) Specifies the URL to define the OAuth 2.0 authorization server. -- `external_oauth_snowflake_user_mapping_attribute` (String) Indicates which Snowflake user record attribute should be used to map the access token to a Snowflake user record. Valid options are: [LOGIN_NAME EMAIL_ADDRESS] -- `external_oauth_token_user_mapping_claim` (Set of String) Specifies the access token claim or claims that can be used to map the access token to a Snowflake user record. -- `external_oauth_type` (String) Specifies the OAuth 2.0 authorization server to be Okta, Microsoft Azure AD, Ping Identity PingFederate, or a Custom OAuth 2.0 authorization server. Valid options are: [OKTA AZURE PING_FEDERATE CUSTOM] +- `external_oauth_snowflake_user_mapping_attribute` (String) Indicates which Snowflake user record attribute should be used to map the access token to a Snowflake user record. Valid values are (case-insensitive): `LOGIN_NAME` | `EMAIL_ADDRESS`. +- `external_oauth_token_user_mapping_claim` (Set of String) Specifies the access token claim or claims that can be used to map the access token to a Snowflake user record. If removed from the config, the resource is recreated. +- `external_oauth_type` (String) Specifies the OAuth 2.0 authorization server to be Okta, Microsoft Azure AD, Ping Identity PingFederate, or a Custom OAuth 2.0 authorization server. Valid values are (case-insensitive): `OKTA` | `AZURE` | `PING_FEDERATE` | `CUSTOM`. - `name` (String) Specifies the name of the External Oath integration. This name follows the rules for Object Identifiers. The name should be unique among security integrations in your account. ### Optional - `comment` (String) Specifies a comment for the OAuth integration. - `external_oauth_allowed_roles_list` (Set of String) Specifies the list of roles that the client can set as the primary role. -- `external_oauth_any_role_mode` (String) Specifies whether the OAuth client or user can use a role that is not defined in the OAuth access token. Valid options are: [DISABLE ENABLE ENABLE_FOR_PRIVILEGE] +- `external_oauth_any_role_mode` (String) Specifies whether the OAuth client or user can use a role that is not defined in the OAuth access token. Valid values are (case-insensitive): `DISABLE` | `ENABLE` | `ENABLE_FOR_PRIVILEGE`. - `external_oauth_audience_list` (Set of String) Specifies additional values that can be used for the access token's audience validation on top of using the Customer's Snowflake Account URL -- `external_oauth_blocked_roles_list` (Set of String) Specifies the list of roles that a client cannot set as the primary role. By default, this list includes the ACCOUNTADMIN, and SECURITYADMIN roles. To remove these privileged roles from the list, use the ALTER ACCOUNT command to set the EXTERNAL_OAUTH_ADD_PRIVILEGED_ROLES_TO_BLOCKED_LIST account parameter to FALSE. -- `external_oauth_jws_keys_url` (Set of String) Specifies the endpoint or a list of endpoints from which to download public keys or certificates to validate an External OAuth access token. The maximum number of URLs that can be specified in the list is 3. -- `external_oauth_rsa_public_key` (String) Specifies a Base64-encoded RSA public key, without the -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY----- headers. -- `external_oauth_rsa_public_key_2` (String) Specifies a second RSA public key, without the -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY----- headers. Used for key rotation. +- `external_oauth_blocked_roles_list` (Set of String) Specifies the list of roles that a client cannot set as the primary role. By default, this list includes the ACCOUNTADMIN, ORGADMIN and SECURITYADMIN roles. To remove these privileged roles from the list, use the ALTER ACCOUNT command to set the EXTERNAL_OAUTH_ADD_PRIVILEGED_ROLES_TO_BLOCKED_LIST account parameter to FALSE. +- `external_oauth_jws_keys_url` (Set of String) Specifies the endpoint or a list of endpoints from which to download public keys or certificates to validate an External OAuth access token. The maximum number of URLs that can be specified in the list is 3. If removed from the config, the resource is recreated. +- `external_oauth_rsa_public_key` (String) Specifies a Base64-encoded RSA public key, without the -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY----- headers. If removed from the config, the resource is recreated. +- `external_oauth_rsa_public_key_2` (String) Specifies a second RSA public key, without the -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY----- headers. Used for key rotation. If removed from the config, the resource is recreated. - `external_oauth_scope_delimiter` (String) Specifies the scope delimiter in the authorization token. -- `external_oauth_scope_mapping_attribute` (String) Specifies the access token claim to map the access token to an account role. +- `external_oauth_scope_mapping_attribute` (String) Specifies the access token claim to map the access token to an account role. If removed from the config, the resource is recreated. ### Read-Only - `describe_output` (List of Object) Outputs the result of `DESCRIBE SECURITY INTEGRATIONS` for the given security integration. (see [below for nested schema](#nestedatt--describe_output)) - `id` (String) The ID of this resource. -- `parameters` (List of Object) Paramteres related to this security integration. (see [below for nested schema](#nestedatt--parameters)) +- `related_parameters` (List of Object) Paramteres related to this security integration. (see [below for nested schema](#nestedatt--related_parameters)) - `show_output` (List of Object) Outputs the result of `SHOW SECURITY INTEGRATIONS` for the given security integration. (see [below for nested schema](#nestedatt--show_output)) @@ -251,15 +251,15 @@ Read-Only: - -### Nested Schema for `parameters` + +### Nested Schema for `related_parameters` Read-Only: -- `external_oauth_add_privileged_roles_to_blocked_list` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--external_oauth_add_privileged_roles_to_blocked_list)) +- `external_oauth_add_privileged_roles_to_blocked_list` (List of Object) (see [below for nested schema](#nestedobjatt--related_parameters--external_oauth_add_privileged_roles_to_blocked_list)) - -### Nested Schema for `parameters.external_oauth_add_privileged_roles_to_blocked_list` + +### Nested Schema for `related_parameters.external_oauth_add_privileged_roles_to_blocked_list` Read-Only: diff --git a/pkg/resources/custom_diffs.go b/pkg/resources/custom_diffs.go index cde4eeb88c..191f1f1081 100644 --- a/pkg/resources/custom_diffs.go +++ b/pkg/resources/custom_diffs.go @@ -97,22 +97,6 @@ func ComputedIfAnyAttributeChanged(key string, changedAttributeKeys ...string) s }) } -// ForceNewIfChangeToEmptyString sets a ForceNew for a string field which was set to an empty value. -func ForceNewIfChangeToEmptyString(key string) schema.CustomizeDiffFunc { - return customdiff.ForceNewIfChange(key, func(ctx context.Context, oldValue, newValue, meta any) bool { - oldString, newString := oldValue.(string), newValue.(string) - return len(oldString) > 0 && len(newString) == 0 - }) -} - -// ForceNewIfChangeToEmptySet sets a ForceNew for a set field which was set to an empty value. -func ForceNewIfChangeToEmptySet(key string) schema.CustomizeDiffFunc { - return customdiff.ForceNewIfChange(key, func(ctx context.Context, oldValue, newValue, meta any) bool { - oldList, newList := oldValue.(*schema.Set).List(), newValue.(*schema.Set).List() - return len(oldList) > 0 && len(newList) == 0 - }) -} - type parameter struct { parameterName sdk.AccountParameter valueType valueType diff --git a/pkg/resources/external_oauth_integration.go b/pkg/resources/external_oauth_integration.go index 5129329546..b021e8d5d6 100644 --- a/pkg/resources/external_oauth_integration.go +++ b/pkg/resources/external_oauth_integration.go @@ -30,7 +30,7 @@ var oauthExternalIntegrationSchema = map[string]*schema.Schema{ "external_oauth_type": { Type: schema.TypeString, Required: true, - Description: fmt.Sprintf("Specifies the OAuth 2.0 authorization server to be Okta, Microsoft Azure AD, Ping Identity PingFederate, or a Custom OAuth 2.0 authorization server. Valid options are: %v", sdk.AllExternalOauthSecurityIntegrationTypes), + Description: fmt.Sprintf("Specifies the OAuth 2.0 authorization server to be Okta, Microsoft Azure AD, Ping Identity PingFederate, or a Custom OAuth 2.0 authorization server. Valid values are (case-insensitive): %s.", possibleValuesListed(sdk.AsStringList(sdk.AllExternalOauthSecurityIntegrationTypes))), ValidateDiagFunc: sdkValidation(sdk.ToExternalOauthSecurityIntegrationTypeOption), DiffSuppressFunc: NormalizeAndCompare(sdk.ToExternalOauthSecurityIntegrationTypeOption), }, @@ -48,12 +48,12 @@ var oauthExternalIntegrationSchema = map[string]*schema.Schema{ Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, Required: true, - Description: "Specifies the access token claim or claims that can be used to map the access token to a Snowflake user record.", + Description: "Specifies the access token claim or claims that can be used to map the access token to a Snowflake user record. If removed from the config, the resource is recreated.", }, "external_oauth_snowflake_user_mapping_attribute": { Type: schema.TypeString, Required: true, - Description: fmt.Sprintf("Indicates which Snowflake user record attribute should be used to map the access token to a Snowflake user record. Valid options are: %v", sdk.AllExternalOauthSecurityIntegrationSnowflakeUserMappingAttributes), + Description: fmt.Sprintf("Indicates which Snowflake user record attribute should be used to map the access token to a Snowflake user record. Valid values are (case-insensitive): %s.", possibleValuesListed(sdk.AsStringList(sdk.AllExternalOauthSecurityIntegrationSnowflakeUserMappingAttributes))), ValidateDiagFunc: sdkValidation(sdk.ToExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeOption), DiffSuppressFunc: NormalizeAndCompare(sdk.ToExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeOption), }, @@ -63,19 +63,19 @@ var oauthExternalIntegrationSchema = map[string]*schema.Schema{ MaxItems: 3, Optional: true, ConflictsWith: []string{"external_oauth_rsa_public_key", "external_oauth_rsa_public_key_2"}, - Description: "Specifies the endpoint or a list of endpoints from which to download public keys or certificates to validate an External OAuth access token. The maximum number of URLs that can be specified in the list is 3.", + Description: "Specifies the endpoint or a list of endpoints from which to download public keys or certificates to validate an External OAuth access token. The maximum number of URLs that can be specified in the list is 3. If removed from the config, the resource is recreated.", }, "external_oauth_rsa_public_key": { Type: schema.TypeString, Optional: true, - Description: "Specifies a Base64-encoded RSA public key, without the -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY----- headers.", + Description: "Specifies a Base64-encoded RSA public key, without the -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY----- headers. If removed from the config, the resource is recreated.", DiffSuppressFunc: ignoreTrimSpaceSuppressFunc, ConflictsWith: []string{"external_oauth_jws_keys_url"}, }, "external_oauth_rsa_public_key_2": { Type: schema.TypeString, Optional: true, - Description: "Specifies a second RSA public key, without the -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY----- headers. Used for key rotation.", + Description: "Specifies a second RSA public key, without the -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY----- headers. Used for key rotation. If removed from the config, the resource is recreated.", DiffSuppressFunc: ignoreTrimSpaceSuppressFunc, ConflictsWith: []string{"external_oauth_jws_keys_url"}, }, @@ -83,7 +83,7 @@ var oauthExternalIntegrationSchema = map[string]*schema.Schema{ Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, Optional: true, - Description: "Specifies the list of roles that a client cannot set as the primary role. By default, this list includes the ACCOUNTADMIN, and SECURITYADMIN roles. To remove these privileged roles from the list, use the ALTER ACCOUNT command to set the EXTERNAL_OAUTH_ADD_PRIVILEGED_ROLES_TO_BLOCKED_LIST account parameter to FALSE.", + Description: "Specifies the list of roles that a client cannot set as the primary role. By default, this list includes the ACCOUNTADMIN, ORGADMIN and SECURITYADMIN roles. To remove these privileged roles from the list, use the ALTER ACCOUNT command to set the EXTERNAL_OAUTH_ADD_PRIVILEGED_ROLES_TO_BLOCKED_LIST account parameter to FALSE.", DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { params := d.Get(ParametersAttributeName).([]any) var found *sdk.Parameter @@ -97,7 +97,7 @@ var oauthExternalIntegrationSchema = map[string]*schema.Schema{ return false } - return slices.Contains([]string{"ACCOUNTADMIN", "SECURITYADMIN"}, old) + return slices.Contains([]string{"ACCOUNTADMIN", "SECURITYADMIN", "ORGADMIN"}, old) }, ConflictsWith: []string{"external_oauth_allowed_roles_list"}, }, @@ -117,7 +117,7 @@ var oauthExternalIntegrationSchema = map[string]*schema.Schema{ "external_oauth_any_role_mode": { Type: schema.TypeString, Optional: true, - Description: fmt.Sprintf("Specifies whether the OAuth client or user can use a role that is not defined in the OAuth access token. Valid options are: %v", sdk.AsStringList(sdk.AllExternalOauthSecurityIntegrationAnyRoleModes)), + Description: fmt.Sprintf("Specifies whether the OAuth client or user can use a role that is not defined in the OAuth access token. Valid values are (case-insensitive): %s.", possibleValuesListed(sdk.AsStringList(sdk.AsStringList(sdk.AllExternalOauthSecurityIntegrationAnyRoleModes)))), ValidateDiagFunc: sdkValidation(sdk.ToExternalOauthSecurityIntegrationAnyRoleModeOption), DiffSuppressFunc: NormalizeAndCompare(sdk.ToExternalOauthSecurityIntegrationAnyRoleModeOption), }, @@ -129,7 +129,7 @@ var oauthExternalIntegrationSchema = map[string]*schema.Schema{ "external_oauth_scope_mapping_attribute": { Type: schema.TypeString, Optional: true, - Description: "Specifies the access token claim to map the access token to an account role.", + Description: "Specifies the access token claim to map the access token to an account role. If removed from the config, the resource is recreated.", }, "comment": { Type: schema.TypeString, @@ -186,6 +186,7 @@ func ExternalOauthIntegration() *schema.Resource { Importer: &schema.ResourceImporter{ StateContext: ImportExternalOauthIntegration, }, + Description: "Resource used to manage external oauth security integrations. For more information, check [documentation](https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-oauth-external).", StateUpgraders: []schema.StateUpgrader{ { @@ -268,10 +269,10 @@ func ImportExternalOauthIntegration(ctx context.Context, d *schema.ResourceData, } var roles []string - if err == nil && (*found).Value == "true" { + if err == nil && found.Value == "true" { unfilteredRoles := sdk.ParseCommaSeparatedStringArray(prop.Value, false) for _, role := range unfilteredRoles { - if !slices.Contains([]string{"ACCOUNTADMIN", "SECURITYADMIN"}, role) { + if !slices.Contains([]string{"ACCOUNTADMIN", "ORGADMIN", "SECURITYADMIN"}, role) { roles = append(roles, role) } } diff --git a/pkg/resources/external_oauth_integration_acceptance_test.go b/pkg/resources/external_oauth_integration_acceptance_test.go index 6e9b2719c5..717f805830 100644 --- a/pkg/resources/external_oauth_integration_acceptance_test.go +++ b/pkg/resources/external_oauth_integration_acceptance_test.go @@ -24,9 +24,9 @@ import ( func TestAcc_ExternalOauthIntegration_basic(t *testing.T) { id := acc.TestClient().Ids.RandomAccountObjectIdentifier() - role1, role1Cleanup := acc.TestClient().Role.CreateRole(t) + role, roleCleanup := acc.TestClient().Role.CreateRole(t) issuer := random.String() - t.Cleanup(role1Cleanup) + t.Cleanup(roleCleanup) m := func(complete bool) map[string]config.Variable { c := map[string]config.Variable{ "enabled": config.BoolVariable(true), @@ -39,7 +39,7 @@ func TestAcc_ExternalOauthIntegration_basic(t *testing.T) { } if complete { c["external_oauth_add_privileged_roles_to_blocked_list"] = config.BoolVariable(true) - c["external_oauth_allowed_roles_list"] = config.SetVariable(config.StringVariable(role1.ID().Name())) + c["external_oauth_allowed_roles_list"] = config.SetVariable(config.StringVariable(role.ID().Name())) c["external_oauth_any_role_mode"] = config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)) c["external_oauth_audience_list"] = config.SetVariable(config.StringVariable("foo")) c["external_oauth_scope_delimiter"] = config.StringVariable(".") @@ -117,7 +117,7 @@ func TestAcc_ExternalOauthIntegration_basic(t *testing.T) { resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "comment", "foo"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "enabled", "true"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_allowed_roles_list.#", "1"), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_allowed_roles_list.0", role1.ID().Name()), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_allowed_roles_list.0", role.ID().Name()), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_any_role_mode", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_audience_list.#", "1"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_audience_list.0", "foo"), @@ -145,7 +145,7 @@ func TestAcc_ExternalOauthIntegration_basic(t *testing.T) { resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_issuer.0.value", issuer), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_jws_keys_url.0.value", "https://example.com"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_any_role_mode.0.value", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_allowed_roles_list.0.value", role1.Name), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_allowed_roles_list.0.value", role.Name), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_audience_list.0.value", "foo"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_token_user_mapping_claim.0.value", "['foo']"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_snowflake_user_mapping_attribute.0.value", string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), @@ -170,7 +170,7 @@ func TestAcc_ExternalOauthIntegration_basic(t *testing.T) { importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_jws_keys_url.0", "https://example.com"), importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_any_role_mode", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_allowed_roles_list.#", "1"), - importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_allowed_roles_list.0", role1.Name), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_allowed_roles_list.0", role.Name), importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_audience_list.#", "1"), importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_audience_list.0", "foo"), importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_scope_delimiter", "."), @@ -198,7 +198,7 @@ func TestAcc_ExternalOauthIntegration_basic(t *testing.T) { resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "comment", "foo"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "enabled", "true"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_allowed_roles_list.#", "1"), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_allowed_roles_list.0", role1.ID().Name()), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_allowed_roles_list.0", role.ID().Name()), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_any_role_mode", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_audience_list.#", "1"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_audience_list.0", "foo"), @@ -226,7 +226,7 @@ func TestAcc_ExternalOauthIntegration_basic(t *testing.T) { resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_issuer.0.value", issuer), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_jws_keys_url.0.value", "https://example.com"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_any_role_mode.0.value", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_allowed_roles_list.0.value", role1.Name), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_allowed_roles_list.0.value", role.Name), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_audience_list.0.value", "foo"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_token_user_mapping_claim.0.value", "['foo']"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_snowflake_user_mapping_attribute.0.value", string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), @@ -285,16 +285,16 @@ func TestAcc_ExternalOauthIntegration_basic(t *testing.T) { func TestAcc_ExternalOauthIntegration_completeWithJwsKeysUrlAndAllowedRolesList(t *testing.T) { id := acc.TestClient().Ids.RandomAccountObjectIdentifier() - role1, role1Cleanup := acc.TestClient().Role.CreateRole(t) + role, roleCleanup := acc.TestClient().Role.CreateRole(t) issuer := random.String() - t.Cleanup(role1Cleanup) + t.Cleanup(roleCleanup) m := func() map[string]config.Variable { return map[string]config.Variable{ "comment": config.StringVariable("foo"), "enabled": config.BoolVariable(true), "external_oauth_add_privileged_roles_to_blocked_list": config.BoolVariable(true), - "external_oauth_allowed_roles_list": config.SetVariable(config.StringVariable(role1.ID().Name())), + "external_oauth_allowed_roles_list": config.SetVariable(config.StringVariable(role.ID().Name())), "external_oauth_any_role_mode": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), "external_oauth_audience_list": config.SetVariable(config.StringVariable("foo")), "external_oauth_issuer": config.StringVariable(issuer), @@ -321,7 +321,7 @@ func TestAcc_ExternalOauthIntegration_completeWithJwsKeysUrlAndAllowedRolesList( resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "comment", "foo"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "enabled", "true"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_allowed_roles_list.#", "1"), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_allowed_roles_list.0", role1.ID().Name()), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_allowed_roles_list.0", role.ID().Name()), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_any_role_mode", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_audience_list.#", "1"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_audience_list.0", "foo"), @@ -349,7 +349,7 @@ func TestAcc_ExternalOauthIntegration_completeWithJwsKeysUrlAndAllowedRolesList( resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_issuer.0.value", issuer), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_jws_keys_url.0.value", "https://example.com"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_any_role_mode.0.value", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_allowed_roles_list.0.value", role1.Name), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_allowed_roles_list.0.value", role.Name), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_audience_list.0.value", "foo"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_token_user_mapping_claim.0.value", "['foo']"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_snowflake_user_mapping_attribute.0.value", string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), @@ -371,9 +371,9 @@ func TestAcc_ExternalOauthIntegration_completeWithJwsKeysUrlAndAllowedRolesList( func TestAcc_ExternalOauthIntegration_completeWithRsaPublicKeysAndBlockedRolesList_paramSet(t *testing.T) { id := acc.TestClient().Ids.RandomAccountObjectIdentifier() - role1, role1Cleanup := acc.TestClient().Role.CreateRole(t) - t.Cleanup(role1Cleanup) - expectedRoles := []string{"ACCOUNTADMIN", "SECURITYADMIN", role1.Name} + role, roleCleanup := acc.TestClient().Role.CreateRole(t) + t.Cleanup(roleCleanup) + expectedRoles := []string{"ACCOUNTADMIN", "SECURITYADMIN", role.Name} sort.Strings(expectedRoles) issuer := random.String() rsaKey, _ := random.GenerateRSAPublicKey(t) @@ -384,7 +384,7 @@ func TestAcc_ExternalOauthIntegration_completeWithRsaPublicKeysAndBlockedRolesLi "comment": config.StringVariable("foo"), "enabled": config.BoolVariable(true), "external_oauth_add_privileged_roles_to_blocked_list": config.BoolVariable(true), - "external_oauth_blocked_roles_list": config.SetVariable(config.StringVariable(role1.ID().Name())), + "external_oauth_blocked_roles_list": config.SetVariable(config.StringVariable(role.ID().Name())), "external_oauth_any_role_mode": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), "external_oauth_audience_list": config.SetVariable(config.StringVariable("foo")), "external_oauth_issuer": config.StringVariable(issuer), @@ -412,7 +412,7 @@ func TestAcc_ExternalOauthIntegration_completeWithRsaPublicKeysAndBlockedRolesLi resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "comment", "foo"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "enabled", "true"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_blocked_roles_list.#", "1"), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_blocked_roles_list.0", role1.ID().Name()), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_blocked_roles_list.0", role.ID().Name()), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_any_role_mode", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_audience_list.#", "1"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_audience_list.0", "foo"), @@ -464,8 +464,8 @@ func TestAcc_ExternalOauthIntegration_completeWithRsaPublicKeysAndBlockedRolesLi func TestAcc_ExternalOauthIntegration_completeWithRsaPublicKeysAndBlockedRolesList_paramUnset(t *testing.T) { id := acc.TestClient().Ids.RandomAccountObjectIdentifier() - role1, role1Cleanup := acc.TestClient().Role.CreateRole(t) - t.Cleanup(role1Cleanup) + role, roleCleanup := acc.TestClient().Role.CreateRole(t) + t.Cleanup(roleCleanup) issuer := random.String() rsaKey, _ := random.GenerateRSAPublicKey(t) paramCleanup := acc.TestClient().Parameter.UpdateAccountParameterTemporarily(t, sdk.AccountParameterExternalOAuthAddPrivilegedRolesToBlockedList, "false") @@ -475,7 +475,7 @@ func TestAcc_ExternalOauthIntegration_completeWithRsaPublicKeysAndBlockedRolesLi "comment": config.StringVariable("foo"), "enabled": config.BoolVariable(true), "external_oauth_add_privileged_roles_to_blocked_list": config.BoolVariable(true), - "external_oauth_blocked_roles_list": config.SetVariable(config.StringVariable(role1.ID().Name())), + "external_oauth_blocked_roles_list": config.SetVariable(config.StringVariable(role.ID().Name())), "external_oauth_any_role_mode": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), "external_oauth_audience_list": config.SetVariable(config.StringVariable("foo")), "external_oauth_issuer": config.StringVariable(issuer), @@ -503,7 +503,7 @@ func TestAcc_ExternalOauthIntegration_completeWithRsaPublicKeysAndBlockedRolesLi resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "comment", "foo"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "enabled", "true"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_blocked_roles_list.#", "1"), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_blocked_roles_list.0", role1.ID().Name()), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_blocked_roles_list.0", role.ID().Name()), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_any_role_mode", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_audience_list.#", "1"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_audience_list.0", "foo"), @@ -532,7 +532,7 @@ func TestAcc_ExternalOauthIntegration_completeWithRsaPublicKeysAndBlockedRolesLi resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_any_role_mode.0.value", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_rsa_public_key.0.value", rsaKey), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_rsa_public_key_2.0.value", rsaKey), - resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_blocked_roles_list.0.value", role1.Name), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_blocked_roles_list.0.value", role.Name), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_allowed_roles_list.0.value", ""), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_audience_list.0.value", "foo"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_token_user_mapping_claim.0.value", "['foo']"), @@ -699,8 +699,8 @@ func TestAcc_ExternalOauthIntegration_InvalidIncomplete(t *testing.T) { func TestAcc_ExternalOauthIntegration_migrateFromVersion092_withRsaPublicKeysAndBlockedRolesList(t *testing.T) { id := acc.TestClient().Ids.RandomAccountObjectIdentifier() - role1, role1Cleanup := acc.TestClient().Role.CreateRole(t) - t.Cleanup(role1Cleanup) + role, roleCleanup := acc.TestClient().Role.CreateRole(t) + t.Cleanup(roleCleanup) issuer := random.String() rsaKey, _ := random.GenerateRSAPublicKey(t) resourceName := "snowflake_external_oauth_integration.test" @@ -718,7 +718,7 @@ func TestAcc_ExternalOauthIntegration_migrateFromVersion092_withRsaPublicKeysAnd Source: "Snowflake-Labs/snowflake", }, }, - Config: externalOauthIntegrationWithRsaPublicKeysAndBlockedRolesListv092(id.Name(), issuer, rsaKey, role1.Name), + Config: externalOauthIntegrationWithRsaPublicKeysAndBlockedRolesListv092(id.Name(), issuer, rsaKey, role.Name), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "name", id.Name()), resource.TestCheckResourceAttr(resourceName, "type", string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), @@ -730,7 +730,7 @@ func TestAcc_ExternalOauthIntegration_migrateFromVersion092_withRsaPublicKeysAnd resource.TestCheckResourceAttr(resourceName, "rsa_public_key", rsaKey), resource.TestCheckResourceAttr(resourceName, "rsa_public_key_2", rsaKey), resource.TestCheckResourceAttr(resourceName, "blocked_roles.#", "1"), - resource.TestCheckResourceAttr(resourceName, "blocked_roles.0", role1.Name), + resource.TestCheckResourceAttr(resourceName, "blocked_roles.0", role.Name), resource.TestCheckResourceAttr(resourceName, "audience_urls.#", "1"), resource.TestCheckResourceAttr(resourceName, "audience_urls.0", "foo"), resource.TestCheckResourceAttr(resourceName, "any_role_mode", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), @@ -739,7 +739,7 @@ func TestAcc_ExternalOauthIntegration_migrateFromVersion092_withRsaPublicKeysAnd }, { ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, - Config: externalOauthIntegrationWithRsaPublicKeysAndBlockedRolesListv093(id.Name(), issuer, rsaKey, role1.Name), + Config: externalOauthIntegrationWithRsaPublicKeysAndBlockedRolesListv093(id.Name(), issuer, rsaKey, role.Name), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "name", id.Name()), resource.TestCheckResourceAttr(resourceName, "external_oauth_type", string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), @@ -751,7 +751,7 @@ func TestAcc_ExternalOauthIntegration_migrateFromVersion092_withRsaPublicKeysAnd resource.TestCheckResourceAttr(resourceName, "external_oauth_rsa_public_key", rsaKey), resource.TestCheckResourceAttr(resourceName, "external_oauth_rsa_public_key_2", rsaKey), resource.TestCheckResourceAttr(resourceName, "external_oauth_blocked_roles_list.#", "1"), - resource.TestCheckResourceAttr(resourceName, "external_oauth_blocked_roles_list.0", role1.Name), + resource.TestCheckResourceAttr(resourceName, "external_oauth_blocked_roles_list.0", role.Name), resource.TestCheckResourceAttr(resourceName, "external_oauth_audience_list.#", "1"), resource.TestCheckResourceAttr(resourceName, "external_oauth_audience_list.0", "foo"), resource.TestCheckResourceAttr(resourceName, "external_oauth_any_role_mode", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), @@ -816,8 +816,8 @@ resource "snowflake_external_oauth_integration" "test" { func TestAcc_ExternalOauthIntegration_migrateFromVersion092_withJwsKeysUrlAndAllowedRolesList(t *testing.T) { id := acc.TestClient().Ids.RandomAccountObjectIdentifier() - role1, role1Cleanup := acc.TestClient().Role.CreateRole(t) - t.Cleanup(role1Cleanup) + role, roleCleanup := acc.TestClient().Role.CreateRole(t) + t.Cleanup(roleCleanup) issuer := random.String() resourceName := "snowflake_external_oauth_integration.test" resource.Test(t, resource.TestCase{ @@ -834,7 +834,7 @@ func TestAcc_ExternalOauthIntegration_migrateFromVersion092_withJwsKeysUrlAndAll Source: "Snowflake-Labs/snowflake", }, }, - Config: externalOauthIntegrationWithJwsKeysUrlAndAllowedRolesListv092(id.Name(), issuer, role1.Name), + Config: externalOauthIntegrationWithJwsKeysUrlAndAllowedRolesListv092(id.Name(), issuer, role.Name), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "name", id.Name()), resource.TestCheckResourceAttr(resourceName, "type", string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), @@ -846,7 +846,7 @@ func TestAcc_ExternalOauthIntegration_migrateFromVersion092_withJwsKeysUrlAndAll resource.TestCheckResourceAttr(resourceName, "jws_keys_urls.#", "1"), resource.TestCheckResourceAttr(resourceName, "jws_keys_urls.0", "https://example.com"), resource.TestCheckResourceAttr(resourceName, "allowed_roles.#", "1"), - resource.TestCheckResourceAttr(resourceName, "allowed_roles.0", role1.Name), + resource.TestCheckResourceAttr(resourceName, "allowed_roles.0", role.Name), resource.TestCheckResourceAttr(resourceName, "audience_urls.#", "1"), resource.TestCheckResourceAttr(resourceName, "audience_urls.0", "foo"), resource.TestCheckResourceAttr(resourceName, "any_role_mode", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), @@ -855,7 +855,7 @@ func TestAcc_ExternalOauthIntegration_migrateFromVersion092_withJwsKeysUrlAndAll }, { ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, - Config: externalOauthIntegrationWithJwsKeysUrlAndAllowedRolesListv093(id.Name(), issuer, role1.Name), + Config: externalOauthIntegrationWithJwsKeysUrlAndAllowedRolesListv093(id.Name(), issuer, role.Name), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "name", id.Name()), resource.TestCheckResourceAttr(resourceName, "external_oauth_type", string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), @@ -867,7 +867,7 @@ func TestAcc_ExternalOauthIntegration_migrateFromVersion092_withJwsKeysUrlAndAll resource.TestCheckResourceAttr(resourceName, "external_oauth_jws_keys_url.#", "1"), resource.TestCheckResourceAttr(resourceName, "external_oauth_jws_keys_url.0", "https://example.com"), resource.TestCheckResourceAttr(resourceName, "external_oauth_allowed_roles_list.#", "1"), - resource.TestCheckResourceAttr(resourceName, "external_oauth_allowed_roles_list.0", role1.Name), + resource.TestCheckResourceAttr(resourceName, "external_oauth_allowed_roles_list.0", role.Name), resource.TestCheckResourceAttr(resourceName, "external_oauth_audience_list.#", "1"), resource.TestCheckResourceAttr(resourceName, "external_oauth_audience_list.0", "foo"), resource.TestCheckResourceAttr(resourceName, "external_oauth_any_role_mode", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), From 248a0154f4a04a2c432c312605d6e25def74a548 Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Fri, 5 Jul 2024 14:17:47 +0200 Subject: [PATCH 10/14] Review fixed 3 --- pkg/resources/external_oauth_integration.go | 57 +++--- ...ernal_oauth_integration_acceptance_test.go | 190 +++++++++--------- pkg/sdk/parsers.go | 25 +-- pkg/sdk/security_integrations_gen.go | 30 +++ pkg/sdk/security_integrations_impl_gen.go | 38 ---- 5 files changed, 170 insertions(+), 170 deletions(-) diff --git a/pkg/resources/external_oauth_integration.go b/pkg/resources/external_oauth_integration.go index b021e8d5d6..5ee4d1df1f 100644 --- a/pkg/resources/external_oauth_integration.go +++ b/pkg/resources/external_oauth_integration.go @@ -6,6 +6,7 @@ import ( "fmt" "reflect" "slices" + "strings" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" @@ -20,6 +21,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) +var privilegedRoles = []string{"ACCOUNTADMIN", "ORGADMIN", "SECURITYADMIN"} + var oauthExternalIntegrationSchema = map[string]*schema.Schema{ "name": { Type: schema.TypeString, @@ -85,19 +88,34 @@ var oauthExternalIntegrationSchema = map[string]*schema.Schema{ Optional: true, Description: "Specifies the list of roles that a client cannot set as the primary role. By default, this list includes the ACCOUNTADMIN, ORGADMIN and SECURITYADMIN roles. To remove these privileged roles from the list, use the ALTER ACCOUNT command to set the EXTERNAL_OAUTH_ADD_PRIVILEGED_ROLES_TO_BLOCKED_LIST account parameter to FALSE.", DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - params := d.Get(ParametersAttributeName).([]any) - var found *sdk.Parameter - for _, v := range params { - param := v.(sdk.Parameter) - if param.Key == "EXTERNAL_OAUTH_ADD_PRIVILEGED_ROLES_TO_BLOCKED_LIST" { - found = ¶m - } + params := d.Get(RelatedParametersAttributeName).([]any) + if len(params) == 0 { + return false } - if found == nil || !helpers.StringToBool(found.Value) { + result := params[0].(map[string]any) + param := result[strings.ToLower(string(sdk.AccountParameterExternalOAuthAddPrivilegedRolesToBlockedList))].([]any) + value := param[0].(map[string]any)["value"] + if !helpers.StringToBool(value.(string)) { return false } - - return slices.Contains([]string{"ACCOUNTADMIN", "SECURITYADMIN", "ORGADMIN"}, old) + if k == "external_oauth_blocked_roles_list.#" { + old, new := d.GetChange("external_oauth_blocked_roles_list") + var numOld, numNew int + oldList := expandStringList(old.(*schema.Set).List()) + newList := expandStringList(new.(*schema.Set).List()) + for _, v := range oldList { + if !slices.Contains(privilegedRoles, v) { + numOld++ + } + } + for _, v := range newList { + if !slices.Contains(privilegedRoles, v) { + numNew++ + } + } + return numOld == numNew + } + return slices.Contains(privilegedRoles, old) }, ConflictsWith: []string{"external_oauth_allowed_roles_list"}, }, @@ -262,24 +280,7 @@ func ImportExternalOauthIntegration(ctx context.Context, d *schema.ResourceData, if prop, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "EXTERNAL_OAUTH_BLOCKED_ROLES_LIST" }); err == nil { - // handle blocked_roles_list - found, err := client.Parameters.ShowAccountParameter(ctx, sdk.AccountParameterExternalOAuthAddPrivilegedRolesToBlockedList) - if err != nil { - return nil, err - } - - var roles []string - if err == nil && found.Value == "true" { - unfilteredRoles := sdk.ParseCommaSeparatedStringArray(prop.Value, false) - for _, role := range unfilteredRoles { - if !slices.Contains([]string{"ACCOUNTADMIN", "ORGADMIN", "SECURITYADMIN"}, role) { - roles = append(roles, role) - } - } - } else { - roles = sdk.ParseCommaSeparatedStringArray(prop.Value, false) - } - + roles := sdk.ParseCommaSeparatedStringArray(prop.Value, false) if err = d.Set("external_oauth_blocked_roles_list", roles); err != nil { return nil, err } diff --git a/pkg/resources/external_oauth_integration_acceptance_test.go b/pkg/resources/external_oauth_integration_acceptance_test.go index 717f805830..0cf4553962 100644 --- a/pkg/resources/external_oauth_integration_acceptance_test.go +++ b/pkg/resources/external_oauth_integration_acceptance_test.go @@ -38,7 +38,6 @@ func TestAcc_ExternalOauthIntegration_basic(t *testing.T) { "external_oauth_jws_keys_url": config.SetVariable(config.StringVariable("https://example.com")), } if complete { - c["external_oauth_add_privileged_roles_to_blocked_list"] = config.BoolVariable(true) c["external_oauth_allowed_roles_list"] = config.SetVariable(config.StringVariable(role.ID().Name())) c["external_oauth_any_role_mode"] = config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)) c["external_oauth_audience_list"] = config.SetVariable(config.StringVariable("foo")) @@ -291,18 +290,17 @@ func TestAcc_ExternalOauthIntegration_completeWithJwsKeysUrlAndAllowedRolesList( m := func() map[string]config.Variable { return map[string]config.Variable{ - "comment": config.StringVariable("foo"), - "enabled": config.BoolVariable(true), - "external_oauth_add_privileged_roles_to_blocked_list": config.BoolVariable(true), - "external_oauth_allowed_roles_list": config.SetVariable(config.StringVariable(role.ID().Name())), - "external_oauth_any_role_mode": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), - "external_oauth_audience_list": config.SetVariable(config.StringVariable("foo")), - "external_oauth_issuer": config.StringVariable(issuer), - "external_oauth_jws_keys_url": config.SetVariable(config.StringVariable("https://example.com")), - "external_oauth_scope_delimiter": config.StringVariable("."), - "external_oauth_scope_mapping_attribute": config.StringVariable("foo"), - "external_oauth_snowflake_user_mapping_attribute": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), - "external_oauth_token_user_mapping_claim": config.SetVariable(config.StringVariable("foo")), + "comment": config.StringVariable("foo"), + "enabled": config.BoolVariable(true), + "external_oauth_allowed_roles_list": config.SetVariable(config.StringVariable(role.ID().Name())), + "external_oauth_any_role_mode": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), + "external_oauth_audience_list": config.SetVariable(config.StringVariable("foo")), + "external_oauth_issuer": config.StringVariable(issuer), + "external_oauth_jws_keys_url": config.SetVariable(config.StringVariable("https://example.com")), + "external_oauth_scope_delimiter": config.StringVariable("."), + "external_oauth_scope_mapping_attribute": config.StringVariable("foo"), + "external_oauth_snowflake_user_mapping_attribute": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), + "external_oauth_token_user_mapping_claim": config.SetVariable(config.StringVariable("foo")), "name": config.StringVariable(id.Name()), "external_oauth_type": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), } @@ -381,19 +379,18 @@ func TestAcc_ExternalOauthIntegration_completeWithRsaPublicKeysAndBlockedRolesLi t.Cleanup(paramCleanup) m := func() map[string]config.Variable { return map[string]config.Variable{ - "comment": config.StringVariable("foo"), - "enabled": config.BoolVariable(true), - "external_oauth_add_privileged_roles_to_blocked_list": config.BoolVariable(true), - "external_oauth_blocked_roles_list": config.SetVariable(config.StringVariable(role.ID().Name())), - "external_oauth_any_role_mode": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), - "external_oauth_audience_list": config.SetVariable(config.StringVariable("foo")), - "external_oauth_issuer": config.StringVariable(issuer), - "external_oauth_rsa_public_key": config.StringVariable(rsaKey), - "external_oauth_rsa_public_key_2": config.StringVariable(rsaKey), - "external_oauth_scope_delimiter": config.StringVariable("."), - "external_oauth_scope_mapping_attribute": config.StringVariable("foo"), - "external_oauth_snowflake_user_mapping_attribute": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), - "external_oauth_token_user_mapping_claim": config.SetVariable(config.StringVariable("foo")), + "comment": config.StringVariable("foo"), + "enabled": config.BoolVariable(true), + "external_oauth_blocked_roles_list": config.SetVariable(config.StringVariable(role.ID().Name())), + "external_oauth_any_role_mode": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), + "external_oauth_audience_list": config.SetVariable(config.StringVariable("foo")), + "external_oauth_issuer": config.StringVariable(issuer), + "external_oauth_rsa_public_key": config.StringVariable(rsaKey), + "external_oauth_rsa_public_key_2": config.StringVariable(rsaKey), + "external_oauth_scope_delimiter": config.StringVariable("."), + "external_oauth_scope_mapping_attribute": config.StringVariable("foo"), + "external_oauth_snowflake_user_mapping_attribute": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), + "external_oauth_token_user_mapping_claim": config.SetVariable(config.StringVariable("foo")), "name": config.StringVariable(id.Name()), "external_oauth_type": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), } @@ -451,12 +448,29 @@ func TestAcc_ExternalOauthIntegration_completeWithRsaPublicKeysAndBlockedRolesLi ), }, { - ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/completeWithRsaPublicKeysAndBlockedRolesList"), - ConfigVariables: m(), - ResourceName: "snowflake_external_oauth_integration.test", - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"external_oauth_rsa_public_key", "external_oauth_rsa_public_key_2", "external_oauth_scope_mapping_attribute"}, + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/completeWithRsaPublicKeysAndBlockedRolesList"), + ConfigVariables: m(), + ResourceName: "snowflake_external_oauth_integration.test", + ImportState: true, + ImportStateCheck: importchecks.ComposeAggregateImportStateCheck( + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "comment", "foo"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "enabled", "true"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_blocked_roles_list.#", "3"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_blocked_roles_list.0", expectedRoles[0]), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_blocked_roles_list.1", expectedRoles[1]), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_blocked_roles_list.2", expectedRoles[2]), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_any_role_mode", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_audience_list.#", "1"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_audience_list.0", "foo"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_issuer", issuer), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_rsa_public_key", rsaKey), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_scope_delimiter", "."), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_snowflake_user_mapping_attribute", string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_token_user_mapping_claim.#", "1"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_token_user_mapping_claim.0", "foo"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "name", id.Name()), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "external_oauth_type", string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), + ), }, }, }) @@ -472,19 +486,18 @@ func TestAcc_ExternalOauthIntegration_completeWithRsaPublicKeysAndBlockedRolesLi t.Cleanup(paramCleanup) m := func() map[string]config.Variable { return map[string]config.Variable{ - "comment": config.StringVariable("foo"), - "enabled": config.BoolVariable(true), - "external_oauth_add_privileged_roles_to_blocked_list": config.BoolVariable(true), - "external_oauth_blocked_roles_list": config.SetVariable(config.StringVariable(role.ID().Name())), - "external_oauth_any_role_mode": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), - "external_oauth_audience_list": config.SetVariable(config.StringVariable("foo")), - "external_oauth_issuer": config.StringVariable(issuer), - "external_oauth_rsa_public_key": config.StringVariable(rsaKey), - "external_oauth_rsa_public_key_2": config.StringVariable(rsaKey), - "external_oauth_scope_delimiter": config.StringVariable("."), - "external_oauth_scope_mapping_attribute": config.StringVariable("foo"), - "external_oauth_snowflake_user_mapping_attribute": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), - "external_oauth_token_user_mapping_claim": config.SetVariable(config.StringVariable("foo")), + "comment": config.StringVariable("foo"), + "enabled": config.BoolVariable(true), + "external_oauth_blocked_roles_list": config.SetVariable(config.StringVariable(role.ID().Name())), + "external_oauth_any_role_mode": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), + "external_oauth_audience_list": config.SetVariable(config.StringVariable("foo")), + "external_oauth_issuer": config.StringVariable(issuer), + "external_oauth_rsa_public_key": config.StringVariable(rsaKey), + "external_oauth_rsa_public_key_2": config.StringVariable(rsaKey), + "external_oauth_scope_delimiter": config.StringVariable("."), + "external_oauth_scope_mapping_attribute": config.StringVariable("foo"), + "external_oauth_snowflake_user_mapping_attribute": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), + "external_oauth_token_user_mapping_claim": config.SetVariable(config.StringVariable("foo")), "name": config.StringVariable(id.Name()), "external_oauth_type": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), } @@ -556,21 +569,20 @@ func TestAcc_ExternalOauthIntegration_completeWithRsaPublicKeysAndBlockedRolesLi func TestAcc_ExternalOauthIntegration_invalidAnyRoleMode(t *testing.T) { m := func() map[string]config.Variable { return map[string]config.Variable{ - "comment": config.StringVariable("foo"), - "enabled": config.BoolVariable(true), - "external_oauth_add_privileged_roles_to_blocked_list": config.BoolVariable(true), - "external_oauth_allowed_roles_list": config.SetVariable(config.StringVariable("foo")), - "external_oauth_any_role_mode": config.StringVariable("invalid"), - "external_oauth_audience_list": config.SetVariable(config.StringVariable("foo")), - "external_oauth_blocked_roles_list": config.SetVariable(config.StringVariable("foo")), - "external_oauth_issuer": config.StringVariable("foo"), - "external_oauth_jws_keys_url": config.SetVariable(config.StringVariable("foo")), - "external_oauth_rsa_public_key": config.StringVariable("foo"), - "external_oauth_rsa_public_key_2": config.StringVariable("foo"), - "external_oauth_scope_delimiter": config.StringVariable("foo"), - "external_oauth_scope_mapping_attribute": config.StringVariable("foo"), - "external_oauth_snowflake_user_mapping_attribute": config.StringVariable("invalid"), - "external_oauth_token_user_mapping_claim": config.SetVariable(config.StringVariable("foo")), + "comment": config.StringVariable("foo"), + "enabled": config.BoolVariable(true), + "external_oauth_allowed_roles_list": config.SetVariable(config.StringVariable("foo")), + "external_oauth_any_role_mode": config.StringVariable("invalid"), + "external_oauth_audience_list": config.SetVariable(config.StringVariable("foo")), + "external_oauth_blocked_roles_list": config.SetVariable(config.StringVariable("foo")), + "external_oauth_issuer": config.StringVariable("foo"), + "external_oauth_jws_keys_url": config.SetVariable(config.StringVariable("foo")), + "external_oauth_rsa_public_key": config.StringVariable("foo"), + "external_oauth_rsa_public_key_2": config.StringVariable("foo"), + "external_oauth_scope_delimiter": config.StringVariable("foo"), + "external_oauth_scope_mapping_attribute": config.StringVariable("foo"), + "external_oauth_snowflake_user_mapping_attribute": config.StringVariable("invalid"), + "external_oauth_token_user_mapping_claim": config.SetVariable(config.StringVariable("foo")), "name": config.StringVariable("foo"), "external_oauth_type": config.StringVariable("invalid"), } @@ -594,21 +606,20 @@ func TestAcc_ExternalOauthIntegration_invalidAnyRoleMode(t *testing.T) { func TestAcc_ExternalOauthIntegration_invalidSnowflakeUserMappingAttribute(t *testing.T) { m := func() map[string]config.Variable { return map[string]config.Variable{ - "comment": config.StringVariable("foo"), - "enabled": config.BoolVariable(true), - "external_oauth_add_privileged_roles_to_blocked_list": config.BoolVariable(true), - "external_oauth_allowed_roles_list": config.SetVariable(config.StringVariable("foo")), - "external_oauth_any_role_mode": config.StringVariable("invalid"), - "external_oauth_audience_list": config.SetVariable(config.StringVariable("foo")), - "external_oauth_blocked_roles_list": config.SetVariable(config.StringVariable("foo")), - "external_oauth_issuer": config.StringVariable("foo"), - "external_oauth_jws_keys_url": config.SetVariable(config.StringVariable("foo")), - "external_oauth_rsa_public_key": config.StringVariable("foo"), - "external_oauth_rsa_public_key_2": config.StringVariable("foo"), - "external_oauth_scope_delimiter": config.StringVariable("foo"), - "external_oauth_scope_mapping_attribute": config.StringVariable("foo"), - "external_oauth_snowflake_user_mapping_attribute": config.StringVariable("invalid"), - "external_oauth_token_user_mapping_claim": config.SetVariable(config.StringVariable("foo")), + "comment": config.StringVariable("foo"), + "enabled": config.BoolVariable(true), + "external_oauth_allowed_roles_list": config.SetVariable(config.StringVariable("foo")), + "external_oauth_any_role_mode": config.StringVariable("invalid"), + "external_oauth_audience_list": config.SetVariable(config.StringVariable("foo")), + "external_oauth_blocked_roles_list": config.SetVariable(config.StringVariable("foo")), + "external_oauth_issuer": config.StringVariable("foo"), + "external_oauth_jws_keys_url": config.SetVariable(config.StringVariable("foo")), + "external_oauth_rsa_public_key": config.StringVariable("foo"), + "external_oauth_rsa_public_key_2": config.StringVariable("foo"), + "external_oauth_scope_delimiter": config.StringVariable("foo"), + "external_oauth_scope_mapping_attribute": config.StringVariable("foo"), + "external_oauth_snowflake_user_mapping_attribute": config.StringVariable("invalid"), + "external_oauth_token_user_mapping_claim": config.SetVariable(config.StringVariable("foo")), "name": config.StringVariable("foo"), "external_oauth_type": config.StringVariable("invalid"), } @@ -632,21 +643,20 @@ func TestAcc_ExternalOauthIntegration_invalidSnowflakeUserMappingAttribute(t *te func TestAcc_ExternalOauthIntegration_invalidOauthType(t *testing.T) { m := func() map[string]config.Variable { return map[string]config.Variable{ - "comment": config.StringVariable("foo"), - "enabled": config.BoolVariable(true), - "external_oauth_add_privileged_roles_to_blocked_list": config.BoolVariable(true), - "external_oauth_allowed_roles_list": config.SetVariable(config.StringVariable("foo")), - "external_oauth_any_role_mode": config.StringVariable("invalid"), - "external_oauth_audience_list": config.SetVariable(config.StringVariable("foo")), - "external_oauth_blocked_roles_list": config.SetVariable(config.StringVariable("foo")), - "external_oauth_issuer": config.StringVariable("foo"), - "external_oauth_jws_keys_url": config.SetVariable(config.StringVariable("foo")), - "external_oauth_rsa_public_key": config.StringVariable("foo"), - "external_oauth_rsa_public_key_2": config.StringVariable("foo"), - "external_oauth_scope_delimiter": config.StringVariable("foo"), - "external_oauth_scope_mapping_attribute": config.StringVariable("foo"), - "external_oauth_snowflake_user_mapping_attribute": config.StringVariable("invalid"), - "external_oauth_token_user_mapping_claim": config.SetVariable(config.StringVariable("foo")), + "comment": config.StringVariable("foo"), + "enabled": config.BoolVariable(true), + "external_oauth_allowed_roles_list": config.SetVariable(config.StringVariable("foo")), + "external_oauth_any_role_mode": config.StringVariable("invalid"), + "external_oauth_audience_list": config.SetVariable(config.StringVariable("foo")), + "external_oauth_blocked_roles_list": config.SetVariable(config.StringVariable("foo")), + "external_oauth_issuer": config.StringVariable("foo"), + "external_oauth_jws_keys_url": config.SetVariable(config.StringVariable("foo")), + "external_oauth_rsa_public_key": config.StringVariable("foo"), + "external_oauth_rsa_public_key_2": config.StringVariable("foo"), + "external_oauth_scope_delimiter": config.StringVariable("foo"), + "external_oauth_scope_mapping_attribute": config.StringVariable("foo"), + "external_oauth_snowflake_user_mapping_attribute": config.StringVariable("invalid"), + "external_oauth_token_user_mapping_claim": config.SetVariable(config.StringVariable("foo")), "name": config.StringVariable("foo"), "external_oauth_type": config.StringVariable("invalid"), } diff --git a/pkg/sdk/parsers.go b/pkg/sdk/parsers.go index 5ca1d1ae82..26dc8b4a07 100644 --- a/pkg/sdk/parsers.go +++ b/pkg/sdk/parsers.go @@ -23,20 +23,17 @@ func ParseTimestampWithOffset(s string, dateTimeFormat string) (string, error) { // 2. Items are separated by commas, and they shouldn't be a part of any item's value // 3. Items can have as many spaces in between, but after separation they will be trimmed and shouldn't be a part of any item's value func ParseCommaSeparatedStringArray(value string, trimQuotes bool) []string { - if strings.HasPrefix(value, "[") && strings.HasSuffix(value, "]") { - if value == "[]" { - return make([]string, 0) - } - list := strings.Trim(value, "[]") - listItems := strings.Split(list, ",") - trimmedListItems := make([]string, len(listItems)) - for i, item := range listItems { - trimmedListItems[i] = strings.TrimSpace(item) - if trimQuotes { - trimmedListItems[i] = strings.Trim(trimmedListItems[i], "'") - } + value = strings.Trim(value, "[]") + if value == "" { + return make([]string, 0) + } + listItems := strings.Split(value, ",") + trimmedListItems := make([]string, len(listItems)) + for i, item := range listItems { + trimmedListItems[i] = strings.TrimSpace(item) + if trimQuotes { + trimmedListItems[i] = strings.Trim(trimmedListItems[i], "'") } - return trimmedListItems } - return make([]string, 0) + return trimmedListItems } diff --git a/pkg/sdk/security_integrations_gen.go b/pkg/sdk/security_integrations_gen.go index 6e862c96e1..71fa19b1e4 100644 --- a/pkg/sdk/security_integrations_gen.go +++ b/pkg/sdk/security_integrations_gen.go @@ -51,6 +51,7 @@ type CreateApiAuthenticationWithClientCredentialsFlowSecurityIntegrationOptions OauthAllowedScopes []AllowedScope `ddl:"parameter,parentheses" sql:"OAUTH_ALLOWED_SCOPES"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } + type AllowedScope struct { Scope string `ddl:"keyword,single_quotes"` } @@ -122,21 +123,27 @@ type CreateExternalOauthSecurityIntegrationOptions struct { ExternalOauthScopeMappingAttribute *string `ddl:"parameter,single_quotes" sql:"EXTERNAL_OAUTH_SCOPE_MAPPING_ATTRIBUTE"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } + type AllowedRolesList struct { AllowedRolesList []AccountObjectIdentifier `ddl:"list,must_parentheses"` } + type BlockedRolesList struct { BlockedRolesList []AccountObjectIdentifier `ddl:"list,must_parentheses"` } + type JwsKeysUrl struct { JwsKeyUrl string `ddl:"keyword,single_quotes"` } + type AudienceList struct { AudienceList []AudienceListItem `ddl:"list,must_parentheses"` } + type AudienceListItem struct { Item string `ddl:"keyword,single_quotes"` } + type TokenUserMappingClaim struct { Claim string `ddl:"keyword,single_quotes"` } @@ -158,6 +165,7 @@ type CreateOauthForPartnerApplicationsSecurityIntegrationOptions struct { BlockedRolesList *BlockedRolesList `ddl:"parameter,parentheses" sql:"BLOCKED_ROLES_LIST"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } + type PreAuthorizedRolesList struct { PreAuthorizedRolesList []AccountObjectIdentifier `ddl:"list,must_parentheses"` } @@ -213,9 +221,11 @@ type CreateSaml2SecurityIntegrationOptions struct { Saml2SnowflakeAcsUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_SNOWFLAKE_ACS_URL"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } + type UserDomain struct { Domain string `ddl:"keyword,single_quotes"` } + type EmailPattern struct { Pattern string `ddl:"keyword,single_quotes"` } @@ -247,6 +257,7 @@ type AlterApiAuthenticationWithClientCredentialsFlowSecurityIntegrationOptions s Set *ApiAuthenticationWithClientCredentialsFlowIntegrationSet `ddl:"list,no_parentheses" sql:"SET"` Unset *ApiAuthenticationWithClientCredentialsFlowIntegrationUnset `ddl:"list,no_parentheses" sql:"UNSET"` } + type ApiAuthenticationWithClientCredentialsFlowIntegrationSet struct { Enabled *bool `ddl:"parameter" sql:"ENABLED"` OauthTokenEndpoint *string `ddl:"parameter,single_quotes" sql:"OAUTH_TOKEN_ENDPOINT"` @@ -259,6 +270,7 @@ type ApiAuthenticationWithClientCredentialsFlowIntegrationSet struct { OauthAllowedScopes []AllowedScope `ddl:"parameter,parentheses" sql:"OAUTH_ALLOWED_SCOPES"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } + type ApiAuthenticationWithClientCredentialsFlowIntegrationUnset struct { Enabled *bool `ddl:"keyword" sql:"ENABLED"` Comment *bool `ddl:"keyword" sql:"COMMENT"` @@ -275,6 +287,7 @@ type AlterApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationOpti Set *ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSet `ddl:"list,no_parentheses" sql:"SET"` Unset *ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationUnset `ddl:"list,no_parentheses" sql:"UNSET"` } + type ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSet struct { Enabled *bool `ddl:"parameter" sql:"ENABLED"` OauthAuthorizationEndpoint *string `ddl:"parameter,single_quotes" sql:"OAUTH_AUTHORIZATION_ENDPOINT"` @@ -287,6 +300,7 @@ type ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSet struct { OauthRefreshTokenValidity *int `ddl:"parameter" sql:"OAUTH_REFRESH_TOKEN_VALIDITY"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } + type ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationUnset struct { Enabled *bool `ddl:"keyword" sql:"ENABLED"` Comment *bool `ddl:"keyword" sql:"COMMENT"` @@ -303,6 +317,7 @@ type AlterApiAuthenticationWithJwtBearerFlowSecurityIntegrationOptions struct { Set *ApiAuthenticationWithJwtBearerFlowIntegrationSet `ddl:"list,no_parentheses" sql:"SET"` Unset *ApiAuthenticationWithJwtBearerFlowIntegrationUnset `ddl:"list,no_parentheses" sql:"UNSET"` } + type ApiAuthenticationWithJwtBearerFlowIntegrationSet struct { Enabled *bool `ddl:"parameter" sql:"ENABLED"` OauthAuthorizationEndpoint *string `ddl:"parameter,single_quotes" sql:"OAUTH_AUTHORIZATION_ENDPOINT"` @@ -315,6 +330,7 @@ type ApiAuthenticationWithJwtBearerFlowIntegrationSet struct { OauthRefreshTokenValidity *int `ddl:"parameter" sql:"OAUTH_REFRESH_TOKEN_VALIDITY"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } + type ApiAuthenticationWithJwtBearerFlowIntegrationUnset struct { Enabled *bool `ddl:"keyword" sql:"ENABLED"` Comment *bool `ddl:"keyword" sql:"COMMENT"` @@ -331,6 +347,7 @@ type AlterExternalOauthSecurityIntegrationOptions struct { Set *ExternalOauthIntegrationSet `ddl:"list,no_parentheses" sql:"SET"` Unset *ExternalOauthIntegrationUnset `ddl:"list,no_parentheses" sql:"UNSET"` } + type ExternalOauthIntegrationSet struct { Enabled *bool `ddl:"parameter" sql:"ENABLED"` ExternalOauthType *ExternalOauthSecurityIntegrationTypeOption `ddl:"parameter" sql:"EXTERNAL_OAUTH_TYPE"` @@ -348,6 +365,7 @@ type ExternalOauthIntegrationSet struct { ExternalOauthScopeMappingAttribute *string `ddl:"parameter,single_quotes" sql:"EXTERNAL_OAUTH_SCOPE_MAPPING_ATTRIBUTE"` Comment *StringAllowEmpty `ddl:"parameter" sql:"COMMENT"` } + type ExternalOauthIntegrationUnset struct { Enabled *bool `ddl:"keyword" sql:"ENABLED"` ExternalOauthAudienceList *bool `ddl:"keyword" sql:"EXTERNAL_OAUTH_AUDIENCE_LIST"` @@ -364,6 +382,7 @@ type AlterOauthForPartnerApplicationsSecurityIntegrationOptions struct { Set *OauthForPartnerApplicationsIntegrationSet `ddl:"list,no_parentheses" sql:"SET"` Unset *OauthForPartnerApplicationsIntegrationUnset `ddl:"list,no_parentheses" sql:"UNSET"` } + type OauthForPartnerApplicationsIntegrationSet struct { Enabled *bool `ddl:"parameter" sql:"ENABLED"` OauthIssueRefreshTokens *bool `ddl:"parameter" sql:"OAUTH_ISSUE_REFRESH_TOKENS"` @@ -373,6 +392,7 @@ type OauthForPartnerApplicationsIntegrationSet struct { BlockedRolesList *BlockedRolesList `ddl:"parameter,parentheses" sql:"BLOCKED_ROLES_LIST"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } + type OauthForPartnerApplicationsIntegrationUnset struct { Enabled *bool `ddl:"keyword" sql:"ENABLED"` OauthUseSecondaryRoles *bool `ddl:"keyword" sql:"OAUTH_USE_SECONDARY_ROLES"` @@ -389,6 +409,7 @@ type AlterOauthForCustomClientsSecurityIntegrationOptions struct { Set *OauthForCustomClientsIntegrationSet `ddl:"list,no_parentheses" sql:"SET"` Unset *OauthForCustomClientsIntegrationUnset `ddl:"list,no_parentheses" sql:"UNSET"` } + type OauthForCustomClientsIntegrationSet struct { Enabled *bool `ddl:"parameter" sql:"ENABLED"` OauthRedirectUri *string `ddl:"parameter,single_quotes" sql:"OAUTH_REDIRECT_URI"` @@ -404,6 +425,7 @@ type OauthForCustomClientsIntegrationSet struct { OauthClientRsaPublicKey2 *string `ddl:"parameter,single_quotes" sql:"OAUTH_CLIENT_RSA_PUBLIC_KEY_2"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } + type OauthForCustomClientsIntegrationUnset struct { Enabled *bool `ddl:"keyword" sql:"ENABLED"` NetworkPolicy *bool `ddl:"keyword" sql:"NETWORK_POLICY"` @@ -424,6 +446,7 @@ type AlterSaml2SecurityIntegrationOptions struct { Unset *Saml2IntegrationUnset `ddl:"list,no_parentheses" sql:"UNSET"` RefreshSaml2SnowflakePrivateKey *bool `ddl:"keyword" sql:"REFRESH SAML2_SNOWFLAKE_PRIVATE_KEY"` } + type Saml2IntegrationSet struct { Enabled *bool `ddl:"parameter" sql:"ENABLED"` Saml2Issuer *string `ddl:"parameter,single_quotes" sql:"SAML2_ISSUER"` @@ -443,6 +466,7 @@ type Saml2IntegrationSet struct { Saml2SnowflakeAcsUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_SNOWFLAKE_ACS_URL"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } + type Saml2IntegrationUnset struct { Saml2ForceAuthn *bool `ddl:"keyword" sql:"SAML2_FORCE_AUTHN"` Saml2RequestedNameidFormat *bool `ddl:"keyword" sql:"SAML2_REQUESTED_NAMEID_FORMAT"` @@ -461,12 +485,14 @@ type AlterScimSecurityIntegrationOptions struct { Set *ScimIntegrationSet `ddl:"list,no_parentheses" sql:"SET"` Unset *ScimIntegrationUnset `ddl:"list,no_parentheses" sql:"UNSET"` } + type ScimIntegrationSet struct { Enabled *bool `ddl:"parameter" sql:"ENABLED"` NetworkPolicy *AccountObjectIdentifier `ddl:"identifier,equals" sql:"NETWORK_POLICY"` SyncPassword *bool `ddl:"parameter" sql:"SYNC_PASSWORD"` Comment *StringAllowEmpty `ddl:"parameter" sql:"COMMENT"` } + type ScimIntegrationUnset struct { Enabled *bool `ddl:"keyword" sql:"ENABLED"` NetworkPolicy *bool `ddl:"keyword" sql:"NETWORK_POLICY"` @@ -487,12 +513,14 @@ type DescribeSecurityIntegrationOptions struct { securityIntegration bool `ddl:"static" sql:"SECURITY INTEGRATION"` name AccountObjectIdentifier `ddl:"identifier"` } + type securityIntegrationDescRow struct { Property string `db:"property"` PropertyType string `db:"property_type"` PropertyValue string `db:"property_value"` PropertyDefault string `db:"property_default"` } + type SecurityIntegrationProperty struct { Name string Type string @@ -514,6 +542,7 @@ type ShowSecurityIntegrationOptions struct { securityIntegrations bool `ddl:"static" sql:"SECURITY INTEGRATIONS"` Like *Like `ddl:"keyword" sql:"LIKE"` } + type securityIntegrationShowRow struct { Name string `db:"name"` Type string `db:"type"` @@ -522,6 +551,7 @@ type securityIntegrationShowRow struct { Comment sql.NullString `db:"comment"` CreatedOn time.Time `db:"created_on"` } + type SecurityIntegration struct { Name string IntegrationType string diff --git a/pkg/sdk/security_integrations_impl_gen.go b/pkg/sdk/security_integrations_impl_gen.go index 5191b058f2..22fdc301f1 100644 --- a/pkg/sdk/security_integrations_impl_gen.go +++ b/pkg/sdk/security_integrations_impl_gen.go @@ -206,25 +206,21 @@ func (r *CreateExternalOauthSecurityIntegrationRequest) toOpts() *CreateExternal ExternalOauthScopeMappingAttribute: r.ExternalOauthScopeMappingAttribute, Comment: r.Comment, } - if r.ExternalOauthBlockedRolesList != nil { opts.ExternalOauthBlockedRolesList = &BlockedRolesList{ BlockedRolesList: r.ExternalOauthBlockedRolesList.BlockedRolesList, } } - if r.ExternalOauthAllowedRolesList != nil { opts.ExternalOauthAllowedRolesList = &AllowedRolesList{ AllowedRolesList: r.ExternalOauthAllowedRolesList.AllowedRolesList, } } - if r.ExternalOauthAudienceList != nil { opts.ExternalOauthAudienceList = &AudienceList{ AudienceList: r.ExternalOauthAudienceList.AudienceList, } } - return opts } @@ -242,7 +238,6 @@ func (r *CreateOauthForPartnerApplicationsSecurityIntegrationRequest) toOpts() * Comment: r.Comment, } - if r.BlockedRolesList != nil { opts.BlockedRolesList = &BlockedRolesList{ BlockedRolesList: r.BlockedRolesList.BlockedRolesList, @@ -271,19 +266,16 @@ func (r *CreateOauthForCustomClientsSecurityIntegrationRequest) toOpts() *Create OauthClientRsaPublicKey2: r.OauthClientRsaPublicKey2, Comment: r.Comment, } - if r.PreAuthorizedRolesList != nil { opts.PreAuthorizedRolesList = &PreAuthorizedRolesList{ PreAuthorizedRolesList: r.PreAuthorizedRolesList.PreAuthorizedRolesList, } } - if r.BlockedRolesList != nil { opts.BlockedRolesList = &BlockedRolesList{ BlockedRolesList: r.BlockedRolesList.BlockedRolesList, } } - return opts } @@ -335,7 +327,6 @@ func (r *AlterApiAuthenticationWithClientCredentialsFlowSecurityIntegrationReque SetTags: r.SetTags, UnsetTags: r.UnsetTags, } - if r.Set != nil { opts.Set = &ApiAuthenticationWithClientCredentialsFlowIntegrationSet{ Enabled: r.Set.Enabled, @@ -350,14 +341,12 @@ func (r *AlterApiAuthenticationWithClientCredentialsFlowSecurityIntegrationReque Comment: r.Set.Comment, } } - if r.Unset != nil { opts.Unset = &ApiAuthenticationWithClientCredentialsFlowIntegrationUnset{ Enabled: r.Unset.Enabled, Comment: r.Unset.Comment, } } - return opts } @@ -368,7 +357,6 @@ func (r *AlterApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegration SetTags: r.SetTags, UnsetTags: r.UnsetTags, } - if r.Set != nil { opts.Set = &ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSet{ Enabled: r.Set.Enabled, @@ -383,14 +371,12 @@ func (r *AlterApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegration Comment: r.Set.Comment, } } - if r.Unset != nil { opts.Unset = &ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationUnset{ Enabled: r.Unset.Enabled, Comment: r.Unset.Comment, } } - return opts } @@ -401,7 +387,6 @@ func (r *AlterApiAuthenticationWithJwtBearerFlowSecurityIntegrationRequest) toOp SetTags: r.SetTags, UnsetTags: r.UnsetTags, } - if r.Set != nil { opts.Set = &ApiAuthenticationWithJwtBearerFlowIntegrationSet{ Enabled: r.Set.Enabled, @@ -416,14 +401,12 @@ func (r *AlterApiAuthenticationWithJwtBearerFlowSecurityIntegrationRequest) toOp Comment: r.Set.Comment, } } - if r.Unset != nil { opts.Unset = &ApiAuthenticationWithJwtBearerFlowIntegrationUnset{ Enabled: r.Unset.Enabled, Comment: r.Unset.Comment, } } - return opts } @@ -434,7 +417,6 @@ func (r *AlterExternalOauthSecurityIntegrationRequest) toOpts() *AlterExternalOa SetTags: r.SetTags, UnsetTags: r.UnsetTags, } - if r.Set != nil { opts.Set = &ExternalOauthIntegrationSet{ Enabled: r.Set.Enabled, @@ -452,33 +434,28 @@ func (r *AlterExternalOauthSecurityIntegrationRequest) toOpts() *AlterExternalOa ExternalOauthScopeMappingAttribute: r.Set.ExternalOauthScopeMappingAttribute, Comment: r.Set.Comment, } - if r.Set.ExternalOauthBlockedRolesList != nil { opts.Set.ExternalOauthBlockedRolesList = &BlockedRolesList{ BlockedRolesList: r.Set.ExternalOauthBlockedRolesList.BlockedRolesList, } } - if r.Set.ExternalOauthAllowedRolesList != nil { opts.Set.ExternalOauthAllowedRolesList = &AllowedRolesList{ AllowedRolesList: r.Set.ExternalOauthAllowedRolesList.AllowedRolesList, } } - if r.Set.ExternalOauthAudienceList != nil { opts.Set.ExternalOauthAudienceList = &AudienceList{ AudienceList: r.Set.ExternalOauthAudienceList.AudienceList, } } } - if r.Unset != nil { opts.Unset = &ExternalOauthIntegrationUnset{ Enabled: r.Unset.Enabled, ExternalOauthAudienceList: r.Unset.ExternalOauthAudienceList, } } - return opts } @@ -489,7 +466,6 @@ func (r *AlterOauthForPartnerApplicationsSecurityIntegrationRequest) toOpts() *A SetTags: r.SetTags, UnsetTags: r.UnsetTags, } - if r.Set != nil { opts.Set = &OauthForPartnerApplicationsIntegrationSet{ Enabled: r.Set.Enabled, @@ -500,21 +476,18 @@ func (r *AlterOauthForPartnerApplicationsSecurityIntegrationRequest) toOpts() *A Comment: r.Set.Comment, } - if r.Set.BlockedRolesList != nil { opts.Set.BlockedRolesList = &BlockedRolesList{ BlockedRolesList: r.Set.BlockedRolesList.BlockedRolesList, } } } - if r.Unset != nil { opts.Unset = &OauthForPartnerApplicationsIntegrationUnset{ Enabled: r.Unset.Enabled, OauthUseSecondaryRoles: r.Unset.OauthUseSecondaryRoles, } } - return opts } @@ -525,7 +498,6 @@ func (r *AlterOauthForCustomClientsSecurityIntegrationRequest) toOpts() *AlterOa SetTags: r.SetTags, UnsetTags: r.UnsetTags, } - if r.Set != nil { opts.Set = &OauthForCustomClientsIntegrationSet{ Enabled: r.Set.Enabled, @@ -541,20 +513,17 @@ func (r *AlterOauthForCustomClientsSecurityIntegrationRequest) toOpts() *AlterOa OauthClientRsaPublicKey2: r.Set.OauthClientRsaPublicKey2, Comment: r.Set.Comment, } - if r.Set.PreAuthorizedRolesList != nil { opts.Set.PreAuthorizedRolesList = &PreAuthorizedRolesList{ PreAuthorizedRolesList: r.Set.PreAuthorizedRolesList.PreAuthorizedRolesList, } } - if r.Set.BlockedRolesList != nil { opts.Set.BlockedRolesList = &BlockedRolesList{ BlockedRolesList: r.Set.BlockedRolesList.BlockedRolesList, } } } - if r.Unset != nil { opts.Unset = &OauthForCustomClientsIntegrationUnset{ Enabled: r.Unset.Enabled, @@ -564,7 +533,6 @@ func (r *AlterOauthForCustomClientsSecurityIntegrationRequest) toOpts() *AlterOa OauthUseSecondaryRoles: r.Unset.OauthUseSecondaryRoles, } } - return opts } @@ -577,7 +545,6 @@ func (r *AlterSaml2SecurityIntegrationRequest) toOpts() *AlterSaml2SecurityInteg RefreshSaml2SnowflakePrivateKey: r.RefreshSaml2SnowflakePrivateKey, } - if r.Set != nil { opts.Set = &Saml2IntegrationSet{ Enabled: r.Set.Enabled, @@ -599,7 +566,6 @@ func (r *AlterSaml2SecurityIntegrationRequest) toOpts() *AlterSaml2SecurityInteg Comment: r.Set.Comment, } } - if r.Unset != nil { opts.Unset = &Saml2IntegrationUnset{ Saml2ForceAuthn: r.Unset.Saml2ForceAuthn, @@ -608,7 +574,6 @@ func (r *AlterSaml2SecurityIntegrationRequest) toOpts() *AlterSaml2SecurityInteg Comment: r.Unset.Comment, } } - return opts } @@ -619,7 +584,6 @@ func (r *AlterScimSecurityIntegrationRequest) toOpts() *AlterScimSecurityIntegra SetTags: r.SetTags, UnsetTags: r.UnsetTags, } - if r.Set != nil { opts.Set = &ScimIntegrationSet{ Enabled: r.Set.Enabled, @@ -628,7 +592,6 @@ func (r *AlterScimSecurityIntegrationRequest) toOpts() *AlterScimSecurityIntegra Comment: r.Set.Comment, } } - if r.Unset != nil { opts.Unset = &ScimIntegrationUnset{ Enabled: r.Unset.Enabled, @@ -636,7 +599,6 @@ func (r *AlterScimSecurityIntegrationRequest) toOpts() *AlterScimSecurityIntegra SyncPassword: r.Unset.SyncPassword, } } - return opts } From 03e2ddcf3e9f85a2bbcad55f2bff3ad9b9a15d25 Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Fri, 5 Jul 2024 16:27:46 +0200 Subject: [PATCH 11/14] Fix tests --- pkg/sdk/parsers_test.go | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/pkg/sdk/parsers_test.go b/pkg/sdk/parsers_test.go index c82a3e50a3..59ecfc7ca6 100644 --- a/pkg/sdk/parsers_test.go +++ b/pkg/sdk/parsers_test.go @@ -29,10 +29,22 @@ func TestParseCommaSeparatedStringArray(t *testing.T) { Result: []string{"one"}, }, { - Name: "multiple elements in list", + Name: "one element in list with quotes", + Value: "['one']", + TrimQuotes: true, + Result: []string{"one"}, + }, + { + Name: "multiple elements with quotes in list", Value: "[one, two, three]", Result: []string{"one", "two", "three"}, }, + { + Name: "multiple elements with quotes in list", + Value: "['one', 'two', 'three']", + TrimQuotes: true, + Result: []string{"one", "two", "three"}, + }, { Name: "multiple elements in list - packed", Value: "[one,two,three]", @@ -46,7 +58,13 @@ func TestParseCommaSeparatedStringArray(t *testing.T) { { Name: "list without brackets", Value: "one,two,three", - Result: []string{}, + Result: []string{"one", "two", "three"}, + }, + { + Name: "list without brackets, elems with quotes", + Value: "'one','two','three'", + TrimQuotes: true, + Result: []string{"one", "two", "three"}, }, } From e9898b6daabde1b8e9ba72ddb1ad61b4adc6b549 Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Mon, 8 Jul 2024 11:03:48 +0200 Subject: [PATCH 12/14] Extract privileged roles description and improve docs --- docs/resources/external_oauth_integration.md | 2 ++ pkg/acceptance/check_destroy.go | 3 ++ pkg/resources/external_oauth_integration.go | 2 +- pkg/resources/special_values.go | 4 +++ .../external_oauth_integration.md.tmpl | 32 +++++++++++++++++++ 5 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 templates/resources/external_oauth_integration.md.tmpl diff --git a/docs/resources/external_oauth_integration.md b/docs/resources/external_oauth_integration.md index 7ef9b1ac27..bb4bf25f1f 100644 --- a/docs/resources/external_oauth_integration.md +++ b/docs/resources/external_oauth_integration.md @@ -5,6 +5,8 @@ description: |- Resource used to manage external oauth security integrations. For more information, check documentation https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-oauth-external. --- +!> **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#v0920--v0930) to use it. + # snowflake_external_oauth_integration (Resource) Resource used to manage external oauth security integrations. For more information, check [documentation](https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-oauth-external). diff --git a/pkg/acceptance/check_destroy.go b/pkg/acceptance/check_destroy.go index 9e2d11420a..4fda590696 100644 --- a/pkg/acceptance/check_destroy.go +++ b/pkg/acceptance/check_destroy.go @@ -91,6 +91,9 @@ var showByIdFunctions = map[resources.Resource]showByIdFunc{ resources.ExternalFunction: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error { return runShowById(ctx, id, client.ExternalFunctions.ShowByID) }, + resources.ExternalOauthSecurityIntegration: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error { + return runShowById(ctx, id, client.SecurityIntegrations.ShowByID) + }, resources.ExternalTable: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error { return runShowById(ctx, id, client.ExternalTables.ShowByID) }, diff --git a/pkg/resources/external_oauth_integration.go b/pkg/resources/external_oauth_integration.go index 5ee4d1df1f..4ff2a7dcc9 100644 --- a/pkg/resources/external_oauth_integration.go +++ b/pkg/resources/external_oauth_integration.go @@ -86,7 +86,7 @@ var oauthExternalIntegrationSchema = map[string]*schema.Schema{ Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, Optional: true, - Description: "Specifies the list of roles that a client cannot set as the primary role. By default, this list includes the ACCOUNTADMIN, ORGADMIN and SECURITYADMIN roles. To remove these privileged roles from the list, use the ALTER ACCOUNT command to set the EXTERNAL_OAUTH_ADD_PRIVILEGED_ROLES_TO_BLOCKED_LIST account parameter to FALSE.", + Description: withPrivilegedRolesDescription("Specifies the list of roles that a client cannot set as the primary role.", string(sdk.AccountParameterExternalOAuthAddPrivilegedRolesToBlockedList)), DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { params := d.Get(RelatedParametersAttributeName).([]any) if len(params) == 0 { diff --git a/pkg/resources/special_values.go b/pkg/resources/special_values.go index c4837cd94a..af55330879 100644 --- a/pkg/resources/special_values.go +++ b/pkg/resources/special_values.go @@ -37,3 +37,7 @@ func booleanStringToBool(value string) (bool, error) { func booleanStringFieldDescription(description string) string { return fmt.Sprintf(`%s Available options are: "%s" or "%s". When the value is not set in the configuration the provider will put "%s" there which means to use the Snowflake default for this value.`, description, BooleanTrue, BooleanFalse, BooleanDefault) } + +func withPrivilegedRolesDescription(description, paramName string) string { + return fmt.Sprintf(`%s By default, this list includes the ACCOUNTADMIN, ORGADMIN and SECURITYADMIN roles. To remove these privileged roles from the list, use the ALTER ACCOUNT command to set the %s account parameter to FALSE. `, description, paramName) +} diff --git a/templates/resources/external_oauth_integration.md.tmpl b/templates/resources/external_oauth_integration.md.tmpl new file mode 100644 index 0000000000..e7f66bbf91 --- /dev/null +++ b/templates/resources/external_oauth_integration.md.tmpl @@ -0,0 +1,32 @@ +--- +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 }} +--- + +!> **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#v0920--v0930) to use it. + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +{{ if .HasExample -}} +## Example Usage + +{{ tffile (printf "examples/resources/%s/resource.tf" .Name)}} +{{- end }} + +{{ .SchemaMarkdown | trimspace }} +{{- if .HasImport }} + +## Import + +Import is supported using the following syntax: + +{{ codefile "shell" (printf "examples/resources/%s/import.sh" .Name)}} +{{- end }} From aa51221dd746b328e352b5df49f8573d30a61998 Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Mon, 8 Jul 2024 14:35:45 +0200 Subject: [PATCH 13/14] Fix tests --- ...ernal_oauth_integration_acceptance_test.go | 84 +++++++++++++++---- .../unset/test.tf | 10 +++ .../unset/variables.tf | 24 ++++++ 3 files changed, 100 insertions(+), 18 deletions(-) create mode 100644 pkg/resources/testdata/TestAcc_ExternalOauthIntegration/unset/test.tf create mode 100644 pkg/resources/testdata/TestAcc_ExternalOauthIntegration/unset/variables.tf diff --git a/pkg/resources/external_oauth_integration_acceptance_test.go b/pkg/resources/external_oauth_integration_acceptance_test.go index 0cf4553962..02b5f57b58 100644 --- a/pkg/resources/external_oauth_integration_acceptance_test.go +++ b/pkg/resources/external_oauth_integration_acceptance_test.go @@ -27,7 +27,7 @@ func TestAcc_ExternalOauthIntegration_basic(t *testing.T) { role, roleCleanup := acc.TestClient().Role.CreateRole(t) issuer := random.String() t.Cleanup(roleCleanup) - m := func(complete bool) map[string]config.Variable { + m := func(complete, unset bool) map[string]config.Variable { c := map[string]config.Variable{ "enabled": config.BoolVariable(true), "name": config.StringVariable(id.Name()), @@ -45,6 +45,9 @@ func TestAcc_ExternalOauthIntegration_basic(t *testing.T) { c["external_oauth_scope_mapping_attribute"] = config.StringVariable("foo") c["comment"] = config.StringVariable("foo") } + if unset { + c["external_oauth_scope_mapping_attribute"] = config.StringVariable("foo") + } return c } resource.Test(t, resource.TestCase{ @@ -58,7 +61,7 @@ func TestAcc_ExternalOauthIntegration_basic(t *testing.T) { // create with empty optionals { ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/basic"), - ConfigVariables: m(false), + ConfigVariables: m(false, false), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "enabled", "true"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_issuer", issuer), @@ -93,7 +96,7 @@ func TestAcc_ExternalOauthIntegration_basic(t *testing.T) { // import - without optionals { ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/basic"), - ConfigVariables: m(false), + ConfigVariables: m(false, false), ResourceName: "snowflake_external_oauth_integration.test", ImportState: true, ImportStateCheck: importchecks.ComposeImportStateCheck( @@ -111,7 +114,7 @@ func TestAcc_ExternalOauthIntegration_basic(t *testing.T) { // set optionals { ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrlAndAllowedRolesList"), - ConfigVariables: m(true), + ConfigVariables: m(true, true), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "comment", "foo"), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "enabled", "true"), @@ -154,7 +157,7 @@ func TestAcc_ExternalOauthIntegration_basic(t *testing.T) { // import - complete { ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrlAndAllowedRolesList"), - ConfigVariables: m(true), + ConfigVariables: m(true, true), ResourceName: "snowflake_external_oauth_integration.test", ImportState: true, ImportStateCheck: importchecks.ComposeImportStateCheck( @@ -179,7 +182,7 @@ func TestAcc_ExternalOauthIntegration_basic(t *testing.T) { // change values externally { ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrlAndAllowedRolesList"), - ConfigVariables: m(true), + ConfigVariables: m(true, true), PreConfig: func() { acc.TestClient().SecurityIntegration.UpdateExternalOauth(t, sdk.NewAlterExternalOauthSecurityIntegrationRequest(id). WithSet(*sdk.NewExternalOauthIntegrationSetRequest(). @@ -232,15 +235,60 @@ func TestAcc_ExternalOauthIntegration_basic(t *testing.T) { resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_scope_delimiter.0.value", "."), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.comment.0.value", "foo")), }, - // unset + // unset without force new { - ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/basic"), - ConfigVariables: m(false), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/unset"), + ConfigVariables: m(false, true), ConfigPlanChecks: resource.ConfigPlanChecks{ PreApply: []plancheck.PlanCheck{ plancheck.ExpectResourceAction("snowflake_external_oauth_integration.test", plancheck.ResourceActionUpdate), }, }, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "comment", ""), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "enabled", "true"), + resource.TestCheckNoResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_allowed_roles_list.#"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_any_role_mode", ""), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_issuer", issuer), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_jws_keys_url.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_jws_keys_url.0", "https://example.com"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_scope_delimiter", ""), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_scope_mapping_attribute", "foo"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_snowflake_user_mapping_attribute", string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_token_user_mapping_claim.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_token_user_mapping_claim.0", "foo"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "name", id.Name()), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "external_oauth_type", string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), + + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.name", id.Name()), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.integration_type", "EXTERNAL_OAUTH - CUSTOM"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.category", "SECURITY"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.enabled", "true"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "show_output.0.comment", ""), + resource.TestCheckResourceAttrSet("snowflake_external_oauth_integration.test", "show_output.0.created_on"), + + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.#", "1"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.enabled.0.value", "true"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_issuer.0.value", issuer), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_jws_keys_url.0.value", "https://example.com"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_any_role_mode.0.value", string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_allowed_roles_list.0.value", ""), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_audience_list.0.value", ""), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_token_user_mapping_claim.0.value", "['foo']"), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_snowflake_user_mapping_attribute.0.value", string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.external_oauth_scope_delimiter.0.value", ","), + resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "describe_output.0.comment.0.value", "")), + }, + // unset all + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/basic"), + ConfigVariables: m(false, true), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction("snowflake_external_oauth_integration.test", plancheck.ResourceActionDestroyBeforeCreate), + }, + }, Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "comment", ""), resource.TestCheckResourceAttr("snowflake_external_oauth_integration.test", "enabled", "true"), @@ -581,10 +629,10 @@ func TestAcc_ExternalOauthIntegration_invalidAnyRoleMode(t *testing.T) { "external_oauth_rsa_public_key_2": config.StringVariable("foo"), "external_oauth_scope_delimiter": config.StringVariable("foo"), "external_oauth_scope_mapping_attribute": config.StringVariable("foo"), - "external_oauth_snowflake_user_mapping_attribute": config.StringVariable("invalid"), + "external_oauth_snowflake_user_mapping_attribute": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), "external_oauth_token_user_mapping_claim": config.SetVariable(config.StringVariable("foo")), "name": config.StringVariable("foo"), - "external_oauth_type": config.StringVariable("invalid"), + "external_oauth_type": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), } } resource.Test(t, resource.TestCase{ @@ -597,7 +645,7 @@ func TestAcc_ExternalOauthIntegration_invalidAnyRoleMode(t *testing.T) { { ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrlAndAllowedRolesList"), ConfigVariables: m(), - ExpectError: regexp.MustCompile(fmt.Sprintf("expected external_oauth_any_role_mode to be one of %q, got invalid", sdk.AsStringList(sdk.AllExternalOauthSecurityIntegrationAnyRoleModes))), + ExpectError: regexp.MustCompile("Error: invalid ExternalOauthSecurityIntegrationAnyRoleModeOption: INVALID"), }, }, }) @@ -609,7 +657,7 @@ func TestAcc_ExternalOauthIntegration_invalidSnowflakeUserMappingAttribute(t *te "comment": config.StringVariable("foo"), "enabled": config.BoolVariable(true), "external_oauth_allowed_roles_list": config.SetVariable(config.StringVariable("foo")), - "external_oauth_any_role_mode": config.StringVariable("invalid"), + "external_oauth_any_role_mode": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), "external_oauth_audience_list": config.SetVariable(config.StringVariable("foo")), "external_oauth_blocked_roles_list": config.SetVariable(config.StringVariable("foo")), "external_oauth_issuer": config.StringVariable("foo"), @@ -621,7 +669,7 @@ func TestAcc_ExternalOauthIntegration_invalidSnowflakeUserMappingAttribute(t *te "external_oauth_snowflake_user_mapping_attribute": config.StringVariable("invalid"), "external_oauth_token_user_mapping_claim": config.SetVariable(config.StringVariable("foo")), "name": config.StringVariable("foo"), - "external_oauth_type": config.StringVariable("invalid"), + "external_oauth_type": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationTypeCustom)), } } resource.Test(t, resource.TestCase{ @@ -634,7 +682,7 @@ func TestAcc_ExternalOauthIntegration_invalidSnowflakeUserMappingAttribute(t *te { ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrlAndAllowedRolesList"), ConfigVariables: m(), - ExpectError: regexp.MustCompile(fmt.Sprintf("expected external_oauth_snowflake_user_mapping_attribute to be one of %q, got invalid", sdk.AsStringList(sdk.AllExternalOauthSecurityIntegrationSnowflakeUserMappingAttributes))), + ExpectError: regexp.MustCompile("Error: invalid ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeOption: INVALID"), }, }, }) @@ -646,7 +694,7 @@ func TestAcc_ExternalOauthIntegration_invalidOauthType(t *testing.T) { "comment": config.StringVariable("foo"), "enabled": config.BoolVariable(true), "external_oauth_allowed_roles_list": config.SetVariable(config.StringVariable("foo")), - "external_oauth_any_role_mode": config.StringVariable("invalid"), + "external_oauth_any_role_mode": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationAnyRoleModeDisable)), "external_oauth_audience_list": config.SetVariable(config.StringVariable("foo")), "external_oauth_blocked_roles_list": config.SetVariable(config.StringVariable("foo")), "external_oauth_issuer": config.StringVariable("foo"), @@ -655,7 +703,7 @@ func TestAcc_ExternalOauthIntegration_invalidOauthType(t *testing.T) { "external_oauth_rsa_public_key_2": config.StringVariable("foo"), "external_oauth_scope_delimiter": config.StringVariable("foo"), "external_oauth_scope_mapping_attribute": config.StringVariable("foo"), - "external_oauth_snowflake_user_mapping_attribute": config.StringVariable("invalid"), + "external_oauth_snowflake_user_mapping_attribute": config.StringVariable(string(sdk.ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress)), "external_oauth_token_user_mapping_claim": config.SetVariable(config.StringVariable("foo")), "name": config.StringVariable("foo"), "external_oauth_type": config.StringVariable("invalid"), @@ -671,7 +719,7 @@ func TestAcc_ExternalOauthIntegration_invalidOauthType(t *testing.T) { { ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalOauthIntegration/completeWithJwsKeysUrlAndAllowedRolesList"), ConfigVariables: m(), - ExpectError: regexp.MustCompile(fmt.Sprintf("expected external_oauth_type to be one of %q, got invalid", sdk.AsStringList(sdk.AllOauthSecurityIntegrationClientTypes))), + ExpectError: regexp.MustCompile("Error: invalid ExternalOauthSecurityIntegrationTypeOption: INVALID"), }, }, }) diff --git a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/unset/test.tf b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/unset/test.tf new file mode 100644 index 0000000000..ba631d3d84 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/unset/test.tf @@ -0,0 +1,10 @@ +resource "snowflake_external_oauth_integration" "test" { + name = var.name + external_oauth_type = var.external_oauth_type + enabled = var.enabled + external_oauth_issuer = var.external_oauth_issuer + external_oauth_token_user_mapping_claim = var.external_oauth_token_user_mapping_claim + external_oauth_snowflake_user_mapping_attribute = var.external_oauth_snowflake_user_mapping_attribute + external_oauth_jws_keys_url = var.external_oauth_jws_keys_url + external_oauth_scope_mapping_attribute = var.external_oauth_scope_mapping_attribute +} diff --git a/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/unset/variables.tf b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/unset/variables.tf new file mode 100644 index 0000000000..54333edfc4 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ExternalOauthIntegration/unset/variables.tf @@ -0,0 +1,24 @@ +variable "name" { + type = string +} +variable "external_oauth_type" { + type = string +} +variable "enabled" { + type = bool +} +variable "external_oauth_issuer" { + type = string +} +variable "external_oauth_snowflake_user_mapping_attribute" { + type = string +} +variable "external_oauth_token_user_mapping_claim" { + type = set(string) +} +variable "external_oauth_jws_keys_url" { + type = set(string) +} +variable "external_oauth_scope_mapping_attribute" { + type = string +} From a20a4d60604f5f8cd536e6f92134905b99d25f73 Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Tue, 9 Jul 2024 12:25:54 +0200 Subject: [PATCH 14/14] Extract diffsuppress --- pkg/resources/diff_suppressions.go | 36 +++++++++++++++++ pkg/resources/external_oauth_integration.go | 43 +++------------------ 2 files changed, 42 insertions(+), 37 deletions(-) diff --git a/pkg/resources/diff_suppressions.go b/pkg/resources/diff_suppressions.go index 3e17034d46..f990094c0d 100644 --- a/pkg/resources/diff_suppressions.go +++ b/pkg/resources/diff_suppressions.go @@ -3,7 +3,10 @@ package resources import ( "fmt" "log" + "slices" + "strings" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -80,3 +83,36 @@ func SuppressIfAny(diffSuppressFunctions ...schema.SchemaDiffSuppressFunc) schem return suppress } } + +func IgnoreValuesFromSetIfParamSet(key, param string, values []string) schema.SchemaDiffSuppressFunc { + return func(k, old, new string, d *schema.ResourceData) bool { + params := d.Get(RelatedParametersAttributeName).([]any) + if len(params) == 0 { + return false + } + result := params[0].(map[string]any) + param := result[strings.ToLower(param)].([]any) + value := param[0].(map[string]any)["value"] + if !helpers.StringToBool(value.(string)) { + return false + } + if k == key+".#" { + old, new := d.GetChange(key) + var numOld, numNew int + oldList := expandStringList(old.(*schema.Set).List()) + newList := expandStringList(new.(*schema.Set).List()) + for _, v := range oldList { + if !slices.Contains(values, v) { + numOld++ + } + } + for _, v := range newList { + if !slices.Contains(values, v) { + numNew++ + } + } + return numOld == numNew + } + return slices.Contains(values, old) + } +} diff --git a/pkg/resources/external_oauth_integration.go b/pkg/resources/external_oauth_integration.go index 4ff2a7dcc9..8e9804acdb 100644 --- a/pkg/resources/external_oauth_integration.go +++ b/pkg/resources/external_oauth_integration.go @@ -5,8 +5,6 @@ import ( "errors" "fmt" "reflect" - "slices" - "strings" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" @@ -83,41 +81,12 @@ var oauthExternalIntegrationSchema = map[string]*schema.Schema{ ConflictsWith: []string{"external_oauth_jws_keys_url"}, }, "external_oauth_blocked_roles_list": { - Type: schema.TypeSet, - Elem: &schema.Schema{Type: schema.TypeString}, - Optional: true, - Description: withPrivilegedRolesDescription("Specifies the list of roles that a client cannot set as the primary role.", string(sdk.AccountParameterExternalOAuthAddPrivilegedRolesToBlockedList)), - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - params := d.Get(RelatedParametersAttributeName).([]any) - if len(params) == 0 { - return false - } - result := params[0].(map[string]any) - param := result[strings.ToLower(string(sdk.AccountParameterExternalOAuthAddPrivilegedRolesToBlockedList))].([]any) - value := param[0].(map[string]any)["value"] - if !helpers.StringToBool(value.(string)) { - return false - } - if k == "external_oauth_blocked_roles_list.#" { - old, new := d.GetChange("external_oauth_blocked_roles_list") - var numOld, numNew int - oldList := expandStringList(old.(*schema.Set).List()) - newList := expandStringList(new.(*schema.Set).List()) - for _, v := range oldList { - if !slices.Contains(privilegedRoles, v) { - numOld++ - } - } - for _, v := range newList { - if !slices.Contains(privilegedRoles, v) { - numNew++ - } - } - return numOld == numNew - } - return slices.Contains(privilegedRoles, old) - }, - ConflictsWith: []string{"external_oauth_allowed_roles_list"}, + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + Description: withPrivilegedRolesDescription("Specifies the list of roles that a client cannot set as the primary role.", string(sdk.AccountParameterExternalOAuthAddPrivilegedRolesToBlockedList)), + DiffSuppressFunc: IgnoreValuesFromSetIfParamSet("external_oauth_blocked_roles_list", string(sdk.AccountParameterExternalOAuthAddPrivilegedRolesToBlockedList), privilegedRoles), + ConflictsWith: []string{"external_oauth_allowed_roles_list"}, }, "external_oauth_allowed_roles_list": { Type: schema.TypeSet,