From 53e7a0aea3350e9e03a804d67e7df796f15bff3a Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Thu, 12 Dec 2024 14:51:32 +0100 Subject: [PATCH] feat: Handle missing fields in function and procedure (#3273) Handle secrets, external access integrations, comments, packages, and snowpark package. --- .../function_describe_snowflake_ext.go | 56 +++++++++++- .../procedure_describe_snowflake_ext.go | 67 +++++++++++++- .../config/model/function_java_model_ext.go | 53 ++++++++++- .../config/model/procedure_java_model_ext.go | 51 +++++++++++ .../function_and_procedure_commons.go | 65 ++++++++++++- pkg/resources/function_commons.go | 20 +++- pkg/resources/function_java.go | 72 ++++++++++++--- .../function_java_acceptance_test.go | 88 +++++++++++++++++- pkg/resources/procedure_commons.go | 16 +++- pkg/resources/procedure_java.go | 57 ++++++++---- .../procedure_java_acceptance_test.go | 89 +++++++++++++++++- pkg/sdk/functions_and_procedures_commons.go | 33 +++++++ pkg/sdk/functions_ext.go | 43 ++++++++- pkg/sdk/procedures_ext.go | 91 +++++++++++++++++-- pkg/sdk/testint/functions_integration_test.go | 48 +++++++++- .../testint/procedures_integration_test.go | 53 ++++++++++- 16 files changed, 839 insertions(+), 63 deletions(-) diff --git a/pkg/acceptance/bettertestspoc/assert/objectassert/function_describe_snowflake_ext.go b/pkg/acceptance/bettertestspoc/assert/objectassert/function_describe_snowflake_ext.go index a4c256b172..b00dad3d3c 100644 --- a/pkg/acceptance/bettertestspoc/assert/objectassert/function_describe_snowflake_ext.go +++ b/pkg/acceptance/bettertestspoc/assert/objectassert/function_describe_snowflake_ext.go @@ -415,7 +415,7 @@ func (f *FunctionDetailsAssert) HasExactlyImportsNormalizedInAnyOrder(imports .. return fmt.Errorf("expected imports to have value; got: nil") } if !assert2.ElementsMatch(t, imports, o.NormalizedImports) { - return fmt.Errorf("expected %v imports in task relations, got %v", imports, o.NormalizedImports) + return fmt.Errorf("expected %v imports, got %v", imports, o.NormalizedImports) } return nil }) @@ -474,3 +474,57 @@ func (f *FunctionDetailsAssert) HasReturnNotNull(expected bool) *FunctionDetails }) return f } + +func (f *FunctionDetailsAssert) HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(integrations ...sdk.AccountObjectIdentifier) *FunctionDetailsAssert { + f.AddAssertion(func(t *testing.T, o *sdk.FunctionDetails) error { + t.Helper() + if o.NormalizedExternalAccessIntegrations == nil { + return fmt.Errorf("expected normalized external access integrations to have value; got: nil") + } + fullyQualifiedNamesExpected := collections.Map(integrations, func(id sdk.AccountObjectIdentifier) string { return id.FullyQualifiedName() }) + fullyQualifiedNamesGot := collections.Map(o.NormalizedExternalAccessIntegrations, func(id sdk.AccountObjectIdentifier) string { return id.FullyQualifiedName() }) + if !assert2.ElementsMatch(t, fullyQualifiedNamesExpected, fullyQualifiedNamesGot) { + return fmt.Errorf("expected %v normalized external access integrations, got %v", integrations, o.NormalizedExternalAccessIntegrations) + } + return nil + }) + return f +} + +func (f *FunctionDetailsAssert) ContainsExactlySecrets(secrets map[string]sdk.SchemaObjectIdentifier) *FunctionDetailsAssert { + f.AddAssertion(func(t *testing.T, o *sdk.FunctionDetails) error { + t.Helper() + if o.NormalizedSecrets == nil { + return fmt.Errorf("expected normalized secrets to have value; got: nil") + } + for k, v := range secrets { + if s, ok := o.NormalizedSecrets[k]; !ok { + return fmt.Errorf("expected normalized secrets to have a secret associated with key %s", k) + } else if s.FullyQualifiedName() != v.FullyQualifiedName() { + return fmt.Errorf("expected secret with key %s to have id %s, got %s", k, v.FullyQualifiedName(), s.FullyQualifiedName()) + } + } + for k := range o.NormalizedSecrets { + if _, ok := secrets[k]; !ok { + return fmt.Errorf("normalized secrets have unexpected key: %s", k) + } + } + + return nil + }) + return f +} + +func (f *FunctionDetailsAssert) HasExactlyPackagesInAnyOrder(packages ...string) *FunctionDetailsAssert { + f.AddAssertion(func(t *testing.T, o *sdk.FunctionDetails) error { + t.Helper() + if o.NormalizedPackages == nil { + return fmt.Errorf("expected packages to have value; got: nil") + } + if !assert2.ElementsMatch(t, packages, o.NormalizedPackages) { + return fmt.Errorf("expected %v packages, got %v", packages, o.NormalizedPackages) + } + return nil + }) + return f +} diff --git a/pkg/acceptance/bettertestspoc/assert/objectassert/procedure_describe_snowflake_ext.go b/pkg/acceptance/bettertestspoc/assert/objectassert/procedure_describe_snowflake_ext.go index 2319b30f7a..962d34f2d6 100644 --- a/pkg/acceptance/bettertestspoc/assert/objectassert/procedure_describe_snowflake_ext.go +++ b/pkg/acceptance/bettertestspoc/assert/objectassert/procedure_describe_snowflake_ext.go @@ -401,7 +401,7 @@ func (f *ProcedureDetailsAssert) HasExactlyImportsNormalizedInAnyOrder(imports . return fmt.Errorf("expected imports to have value; got: nil") } if !assert2.ElementsMatch(t, imports, o.NormalizedImports) { - return fmt.Errorf("expected %v imports in task relations, got %v", imports, o.NormalizedImports) + return fmt.Errorf("expected %v imports, got %v", imports, o.NormalizedImports) } return nil }) @@ -460,3 +460,68 @@ func (f *ProcedureDetailsAssert) HasReturnNotNull(expected bool) *ProcedureDetai }) return f } + +func (f *ProcedureDetailsAssert) HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(integrations ...sdk.AccountObjectIdentifier) *ProcedureDetailsAssert { + f.AddAssertion(func(t *testing.T, o *sdk.ProcedureDetails) error { + t.Helper() + if o.NormalizedExternalAccessIntegrations == nil { + return fmt.Errorf("expected normalized external access integrations to have value; got: nil") + } + fullyQualifiedNamesExpected := collections.Map(integrations, func(id sdk.AccountObjectIdentifier) string { return id.FullyQualifiedName() }) + fullyQualifiedNamesGot := collections.Map(o.NormalizedExternalAccessIntegrations, func(id sdk.AccountObjectIdentifier) string { return id.FullyQualifiedName() }) + if !assert2.ElementsMatch(t, fullyQualifiedNamesExpected, fullyQualifiedNamesGot) { + return fmt.Errorf("expected %v normalized external access integrations, got %v", integrations, o.NormalizedExternalAccessIntegrations) + } + return nil + }) + return f +} + +func (f *ProcedureDetailsAssert) ContainsExactlySecrets(secrets map[string]sdk.SchemaObjectIdentifier) *ProcedureDetailsAssert { + f.AddAssertion(func(t *testing.T, o *sdk.ProcedureDetails) error { + t.Helper() + if o.NormalizedSecrets == nil { + return fmt.Errorf("expected normalized secrets to have value; got: nil") + } + for k, v := range secrets { + if s, ok := o.NormalizedSecrets[k]; !ok { + return fmt.Errorf("expected normalized secrets to have a secret associated with key %s", k) + } else if s.FullyQualifiedName() != v.FullyQualifiedName() { + return fmt.Errorf("expected secret with key %s to have id %s, got %s", k, v.FullyQualifiedName(), s.FullyQualifiedName()) + } + } + for k := range o.NormalizedSecrets { + if _, ok := secrets[k]; !ok { + return fmt.Errorf("normalized secrets have unexpected key: %s", k) + } + } + + return nil + }) + return f +} + +func (f *ProcedureDetailsAssert) HasExactlyPackagesInAnyOrder(packages ...string) *ProcedureDetailsAssert { + f.AddAssertion(func(t *testing.T, o *sdk.ProcedureDetails) error { + t.Helper() + if o.NormalizedPackages == nil { + return fmt.Errorf("expected packages to have value; got: nil") + } + if !assert2.ElementsMatch(t, packages, o.NormalizedPackages) { + return fmt.Errorf("expected %v packages, got %v", packages, o.NormalizedPackages) + } + return nil + }) + return f +} + +func (f *ProcedureDetailsAssert) HasSnowparkVersion(expected string) *ProcedureDetailsAssert { + f.AddAssertion(func(t *testing.T, o *sdk.ProcedureDetails) error { + t.Helper() + if o.SnowparkVersion != expected { + return fmt.Errorf("expected snowpark version %s; got: %s", expected, o.SnowparkVersion) + } + return nil + }) + return f +} diff --git a/pkg/acceptance/bettertestspoc/config/model/function_java_model_ext.go b/pkg/acceptance/bettertestspoc/config/model/function_java_model_ext.go index 8579ea981a..0174c4dc10 100644 --- a/pkg/acceptance/bettertestspoc/config/model/function_java_model_ext.go +++ b/pkg/acceptance/bettertestspoc/config/model/function_java_model_ext.go @@ -2,9 +2,11 @@ package model import ( "encoding/json" + "strings" tfconfig "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/datatypes" ) @@ -69,13 +71,62 @@ func (f *FunctionJavaModel) WithImport(stageLocation string, pathOnStage string) return f.WithImportsValue( tfconfig.ObjectVariable( map[string]tfconfig.Variable{ - "stage_location": tfconfig.StringVariable(stageLocation), + "stage_location": tfconfig.StringVariable(strings.TrimPrefix(stageLocation, "@")), "path_on_stage": tfconfig.StringVariable(pathOnStage), }, ), ) } +func (f *FunctionJavaModel) WithImports(imports ...sdk.NormalizedPath) *FunctionJavaModel { + return f.WithImportsValue( + tfconfig.SetVariable( + collections.Map(imports, func(imp sdk.NormalizedPath) tfconfig.Variable { + return tfconfig.ObjectVariable( + map[string]tfconfig.Variable{ + "stage_location": tfconfig.StringVariable(imp.StageLocation), + "path_on_stage": tfconfig.StringVariable(imp.PathOnStage), + }, + ) + })..., + ), + ) +} + +func (f *FunctionJavaModel) WithPackages(pkgs ...string) *FunctionJavaModel { + return f.WithPackagesValue( + tfconfig.SetVariable( + collections.Map(pkgs, func(pkg string) tfconfig.Variable { return tfconfig.StringVariable(pkg) })..., + ), + ) +} + +func (f *FunctionJavaModel) WithExternalAccessIntegrations(ids ...sdk.AccountObjectIdentifier) *FunctionJavaModel { + return f.WithExternalAccessIntegrationsValue( + tfconfig.SetVariable( + collections.Map(ids, func(id sdk.AccountObjectIdentifier) tfconfig.Variable { return tfconfig.StringVariable(id.Name()) })..., + ), + ) +} + +func (f *FunctionJavaModel) WithSecrets(secrets map[string]sdk.SchemaObjectIdentifier) *FunctionJavaModel { + objects := make([]tfconfig.Variable, 0) + for k, v := range secrets { + objects = append(objects, tfconfig.ObjectVariable( + map[string]tfconfig.Variable{ + "secret_variable_name": tfconfig.StringVariable(k), + "secret_id": tfconfig.StringVariable(v.FullyQualifiedName()), + }, + )) + } + + return f.WithSecretsValue( + tfconfig.SetVariable( + objects..., + ), + ) +} + func (f *FunctionJavaModel) WithTargetPathParts(stageLocation string, pathOnStage string) *FunctionJavaModel { return f.WithTargetPathValue( tfconfig.ObjectVariable( diff --git a/pkg/acceptance/bettertestspoc/config/model/procedure_java_model_ext.go b/pkg/acceptance/bettertestspoc/config/model/procedure_java_model_ext.go index cb6779784c..b8a1602f79 100644 --- a/pkg/acceptance/bettertestspoc/config/model/procedure_java_model_ext.go +++ b/pkg/acceptance/bettertestspoc/config/model/procedure_java_model_ext.go @@ -3,6 +3,8 @@ package model import ( "encoding/json" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" + tfconfig "github.com/hashicorp/terraform-plugin-testing/config" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" @@ -77,6 +79,55 @@ func (f *ProcedureJavaModel) WithImport(stageLocation string, pathOnStage string ) } +func (f *ProcedureJavaModel) WithImports(imports ...sdk.NormalizedPath) *ProcedureJavaModel { + return f.WithImportsValue( + tfconfig.SetVariable( + collections.Map(imports, func(imp sdk.NormalizedPath) tfconfig.Variable { + return tfconfig.ObjectVariable( + map[string]tfconfig.Variable{ + "stage_location": tfconfig.StringVariable(imp.StageLocation), + "path_on_stage": tfconfig.StringVariable(imp.PathOnStage), + }, + ) + })..., + ), + ) +} + +func (f *ProcedureJavaModel) WithPackages(pkgs ...string) *ProcedureJavaModel { + return f.WithPackagesValue( + tfconfig.SetVariable( + collections.Map(pkgs, func(pkg string) tfconfig.Variable { return tfconfig.StringVariable(pkg) })..., + ), + ) +} + +func (f *ProcedureJavaModel) WithExternalAccessIntegrations(ids ...sdk.AccountObjectIdentifier) *ProcedureJavaModel { + return f.WithExternalAccessIntegrationsValue( + tfconfig.SetVariable( + collections.Map(ids, func(id sdk.AccountObjectIdentifier) tfconfig.Variable { return tfconfig.StringVariable(id.Name()) })..., + ), + ) +} + +func (f *ProcedureJavaModel) WithSecrets(secrets map[string]sdk.SchemaObjectIdentifier) *ProcedureJavaModel { + objects := make([]tfconfig.Variable, 0) + for k, v := range secrets { + objects = append(objects, tfconfig.ObjectVariable( + map[string]tfconfig.Variable{ + "secret_variable_name": tfconfig.StringVariable(k), + "secret_id": tfconfig.StringVariable(v.FullyQualifiedName()), + }, + )) + } + + return f.WithSecretsValue( + tfconfig.SetVariable( + objects..., + ), + ) +} + func (f *ProcedureJavaModel) WithTargetPathParts(stageLocation string, pathOnStage string) *ProcedureJavaModel { return f.WithTargetPathValue( tfconfig.ObjectVariable( diff --git a/pkg/resources/function_and_procedure_commons.go b/pkg/resources/function_and_procedure_commons.go index e21801813c..213217e968 100644 --- a/pkg/resources/function_and_procedure_commons.go +++ b/pkg/resources/function_and_procedure_commons.go @@ -10,7 +10,7 @@ import ( func readFunctionOrProcedureArguments(d *schema.ResourceData, args []sdk.NormalizedArgument) error { if len(args) == 0 { - // TODO [SNOW-1348103]: handle empty list + // TODO [before V1]: handle empty list return nil } // We do it the unusual way because the default values are not returned by SF. @@ -40,6 +40,21 @@ func readFunctionOrProcedureImports(d *schema.ResourceData, imports []sdk.Normal return d.Set("imports", imps) } +func readFunctionOrProcedureExternalAccessIntegrations(d *schema.ResourceData, externalAccessIntegrations []sdk.AccountObjectIdentifier) error { + return d.Set("external_access_integrations", collections.Map(externalAccessIntegrations, func(id sdk.AccountObjectIdentifier) string { return id.Name() })) +} + +func readFunctionOrProcedureSecrets(d *schema.ResourceData, secrets map[string]sdk.SchemaObjectIdentifier) error { + all := make([]map[string]any, 0) + for k, v := range secrets { + all = append(all, map[string]any{ + "secret_variable_name": k, + "secret_id": v.FullyQualifiedName(), + }) + } + return d.Set("secrets", all) +} + func readFunctionOrProcedureTargetPath(d *schema.ResourceData, normalizedPath *sdk.NormalizedPath) error { if normalizedPath == nil { // don't do anything if imports not present @@ -52,3 +67,51 @@ func readFunctionOrProcedureTargetPath(d *schema.ResourceData, normalizedPath *s } return d.Set("target_path", tp) } + +func setExternalAccessIntegrationsInBuilder[T any](d *schema.ResourceData, setIntegrations func([]sdk.AccountObjectIdentifier) T) error { + integrations, err := parseExternalAccessIntegrationsCommon(d) + if err != nil { + return err + } + setIntegrations(integrations) + return nil +} + +func setSecretsInBuilder[T any](d *schema.ResourceData, setSecrets func([]sdk.SecretReference) T) error { + secrets, err := parseSecretsCommon(d) + if err != nil { + return err + } + setSecrets(secrets) + return nil +} + +func parseExternalAccessIntegrationsCommon(d *schema.ResourceData) ([]sdk.AccountObjectIdentifier, error) { + integrations := make([]sdk.AccountObjectIdentifier, 0) + if v, ok := d.GetOk("external_access_integrations"); ok { + for _, i := range v.(*schema.Set).List() { + id, err := sdk.ParseAccountObjectIdentifier(i.(string)) + if err != nil { + return nil, err + } + integrations = append(integrations, id) + } + } + return integrations, nil +} + +func parseSecretsCommon(d *schema.ResourceData) ([]sdk.SecretReference, error) { + secretReferences := make([]sdk.SecretReference, 0) + if v, ok := d.GetOk("secrets"); ok { + for _, s := range v.(*schema.Set).List() { + name := s.(map[string]any)["secret_variable_name"].(string) + idRaw := s.(map[string]any)["secret_id"].(string) + id, err := sdk.ParseSchemaObjectIdentifier(idRaw) + if err != nil { + return nil, err + } + secretReferences = append(secretReferences, sdk.SecretReference{VariableName: name, Name: id}) + } + } + return secretReferences, nil +} diff --git a/pkg/resources/function_commons.go b/pkg/resources/function_commons.go index fe5a097a45..7dddd097e7 100644 --- a/pkg/resources/function_commons.go +++ b/pkg/resources/function_commons.go @@ -316,7 +316,6 @@ func functionBaseSchema() map[string]schema.Schema { ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), }, Optional: true, - ForceNew: true, Description: "The names of [external access integrations](https://docs.snowflake.com/en/sql-reference/sql/create-external-access-integration) needed in order for this function’s handler code to access external networks. An external access integration specifies [network rules](https://docs.snowflake.com/en/sql-reference/sql/create-network-rule) and [secrets](https://docs.snowflake.com/en/sql-reference/sql/create-secret) that specify external locations and credentials (if any) allowed for use by handler code when making requests of an external network, such as an external REST API.", }, "secrets": { @@ -441,6 +440,16 @@ func parseFunctionImportsCommon(d *schema.ResourceData) ([]sdk.FunctionImportReq return imports, nil } +func parseFunctionPackagesCommon(d *schema.ResourceData) ([]sdk.FunctionPackageRequest, error) { + packages := make([]sdk.FunctionPackageRequest, 0) + if v, ok := d.GetOk("packages"); ok { + for _, pkg := range v.(*schema.Set).List() { + packages = append(packages, *sdk.NewFunctionPackageRequest().WithPackage(pkg.(string))) + } + } + return packages, nil +} + func parseFunctionTargetPathCommon(d *schema.ResourceData) (string, error) { var tp string if v, ok := d.GetOk("target_path"); ok { @@ -482,6 +491,15 @@ func setFunctionImportsInBuilder[T any](d *schema.ResourceData, setImports func( return nil } +func setFunctionPackagesInBuilder[T any](d *schema.ResourceData, setPackages func([]sdk.FunctionPackageRequest) T) error { + packages, err := parseFunctionPackagesCommon(d) + if err != nil { + return err + } + setPackages(packages) + return nil +} + func setFunctionTargetPathInBuilder[T any](d *schema.ResourceData, setTargetPath func(string) T) error { tp, err := parseFunctionTargetPathCommon(d) if err != nil { diff --git a/pkg/resources/function_java.go b/pkg/resources/function_java.go index c8fca3c13f..e085fc0c97 100644 --- a/pkg/resources/function_java.go +++ b/pkg/resources/function_java.go @@ -72,12 +72,11 @@ func CreateContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta attributeMappedValueCreateBuilder[string](d, "null_input_behavior", request.WithNullInputBehavior, sdk.ToNullInputBehavior), attributeMappedValueCreateBuilder[string](d, "return_results_behavior", request.WithReturnResultsBehavior, sdk.ToReturnResultsBehavior), stringAttributeCreateBuilder(d, "runtime_version", request.WithRuntimeVersion), - // TODO [SNOW-1348103]: handle the rest of the attributes - // comment + stringAttributeCreateBuilder(d, "comment", request.WithComment), setFunctionImportsInBuilder(d, request.WithImports), - // packages - // external_access_integrations - // secrets + setFunctionPackagesInBuilder(d, request.WithPackages), + setExternalAccessIntegrationsInBuilder(d, request.WithExternalAccessIntegrations), + setSecretsInBuilder(d, request.WithSecrets), setFunctionTargetPathInBuilder(d, request.WithTargetPath), stringAttributeCreateBuilder(d, "function_definition", request.WithFunctionDefinitionWrapped), ) @@ -121,19 +120,18 @@ func ReadContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta a // TODO [SNOW-1348103]: handle setting state to value from config errs := errors.Join( - // TODO [SNOW-1348103]: set the rest of the fields // not reading is_secure on purpose (handled as external change to show output) readFunctionOrProcedureArguments(d, allFunctionDetails.functionDetails.NormalizedArguments), d.Set("return_type", allFunctionDetails.functionDetails.ReturnDataType.ToSql()), // not reading null_input_behavior on purpose (handled as external change to show output) // not reading return_results_behavior on purpose (handled as external change to show output) setOptionalFromStringPtr(d, "runtime_version", allFunctionDetails.functionDetails.RuntimeVersion), - // comment + d.Set("comment", allFunctionDetails.function.Description), readFunctionOrProcedureImports(d, allFunctionDetails.functionDetails.NormalizedImports), - // packages + d.Set("packages", allFunctionDetails.functionDetails.NormalizedPackages), setRequiredFromStringPtr(d, "handler", allFunctionDetails.functionDetails.Handler), - // external_access_integrations - // secrets + readFunctionOrProcedureExternalAccessIntegrations(d, allFunctionDetails.functionDetails.NormalizedExternalAccessIntegrations), + readFunctionOrProcedureSecrets(d, allFunctionDetails.functionDetails.NormalizedSecrets), readFunctionOrProcedureTargetPath(d, allFunctionDetails.functionDetails.NormalizedTargetPath), setOptionalFromStringPtr(d, "function_definition", allFunctionDetails.functionDetails.Body), d.Set("function_language", allFunctionDetails.functionDetails.Language), @@ -173,11 +171,32 @@ func UpdateContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta setRequest := sdk.NewFunctionSetRequest() unsetRequest := sdk.NewFunctionUnsetRequest() - // TODO [SNOW-1348103]: handle all updates - // secure - // external access integration - // secrets - // comment + err = errors.Join( + stringAttributeUpdate(d, "comment", &setRequest.Comment, &unsetRequest.Comment), + func() error { + if d.HasChange("secrets") { + return setSecretsInBuilder(d, func(references []sdk.SecretReference) *sdk.FunctionSetRequest { + return setRequest.WithSecretsList(sdk.SecretsListRequest{SecretsList: references}) + }) + } + return nil + }(), + func() error { + if d.HasChange("external_access_integrations") { + return setExternalAccessIntegrationsInBuilder(d, func(references []sdk.AccountObjectIdentifier) any { + if len(references) == 0 { + return unsetRequest.WithExternalAccessIntegrations(true) + } else { + return setRequest.WithExternalAccessIntegrations(references) + } + }) + } + return nil + }(), + ) + if err != nil { + return diag.FromErr(err) + } if updateParamDiags := handleFunctionParametersUpdate(d, setRequest, unsetRequest); len(updateParamDiags) > 0 { return updateParamDiags @@ -187,15 +206,38 @@ func UpdateContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta if !reflect.DeepEqual(*setRequest, *sdk.NewFunctionSetRequest()) { err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithSet(*setRequest)) if err != nil { + d.Partial(true) return diag.FromErr(err) } } if !reflect.DeepEqual(*unsetRequest, *sdk.NewFunctionUnsetRequest()) { err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithUnset(*unsetRequest)) if err != nil { + d.Partial(true) return diag.FromErr(err) } } + // has to be handled separately + if d.HasChange("is_secure") { + if v := d.Get("is_secure").(string); v != BooleanDefault { + parsed, err := booleanStringToBool(v) + if err != nil { + return diag.FromErr(err) + } + err = client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithSetSecure(parsed)) + if err != nil { + d.Partial(true) + return diag.FromErr(err) + } + } else { + err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithUnsetSecure(true)) + if err != nil { + d.Partial(true) + return diag.FromErr(err) + } + } + } + return ReadContextFunctionJava(ctx, d, meta) } diff --git a/pkg/resources/function_java_acceptance_test.go b/pkg/resources/function_java_acceptance_test.go index b805187b69..9b8f032779 100644 --- a/pkg/resources/function_java_acceptance_test.go +++ b/pkg/resources/function_java_acceptance_test.go @@ -173,6 +173,27 @@ func TestAcc_FunctionJava_InlineFull(t *testing.T) { stage, stageCleanup := acc.TestClient().Stage.CreateStage(t) t.Cleanup(stageCleanup) + secretId := acc.TestClient().Ids.RandomSchemaObjectIdentifier() + secretId2 := acc.TestClient().Ids.RandomSchemaObjectIdentifier() + + networkRule, networkRuleCleanup := acc.TestClient().NetworkRule.Create(t) + t.Cleanup(networkRuleCleanup) + + secret, secretCleanup := acc.TestClient().Secret.CreateWithGenericString(t, secretId, "test_secret_string") + t.Cleanup(secretCleanup) + + secret2, secret2Cleanup := acc.TestClient().Secret.CreateWithGenericString(t, secretId2, "test_secret_string_2") + t.Cleanup(secret2Cleanup) + + externalAccessIntegration, externalAccessIntegrationCleanup := acc.TestClient().ExternalAccessIntegration.CreateExternalAccessIntegrationWithNetworkRuleAndSecret(t, networkRule.ID(), secret.ID()) + t.Cleanup(externalAccessIntegrationCleanup) + + externalAccessIntegration2, externalAccessIntegration2Cleanup := acc.TestClient().ExternalAccessIntegration.CreateExternalAccessIntegrationWithNetworkRuleAndSecret(t, networkRule.ID(), secret2.ID()) + t.Cleanup(externalAccessIntegration2Cleanup) + + tmpJavaFunction := acc.TestClient().CreateSampleJavaFunctionAndJarOnUserStage(t) + tmpJavaFunction2 := acc.TestClient().CreateSampleJavaFunctionAndJarOnUserStage(t) + className := "TestFunc" funcName := "echoVarchar" argName := "x" @@ -187,8 +208,34 @@ func TestAcc_FunctionJava_InlineFull(t *testing.T) { functionModel := model.FunctionJavaBasicInline("w", id, dataType, handler, definition). WithArgument(argName, dataType). + WithImports( + sdk.NormalizedPath{StageLocation: "~", PathOnStage: tmpJavaFunction.JarName}, + sdk.NormalizedPath{StageLocation: "~", PathOnStage: tmpJavaFunction2.JarName}, + ). + WithPackages("com.snowflake:snowpark:1.14.0", "com.snowflake:telemetry:0.1.0"). + WithExternalAccessIntegrations(externalAccessIntegration, externalAccessIntegration2). + WithSecrets(map[string]sdk.SchemaObjectIdentifier{ + "abc": secretId, + "def": secretId2, + }). WithTargetPathParts(stage.ID().FullyQualifiedName(), jarName). - WithRuntimeVersion("11") + WithRuntimeVersion("11"). + WithComment("some comment") + + functionModelUpdateWithoutRecreation := model.FunctionJavaBasicInline("w", id, dataType, handler, definition). + WithArgument(argName, dataType). + WithImports( + sdk.NormalizedPath{StageLocation: "~", PathOnStage: tmpJavaFunction.JarName}, + sdk.NormalizedPath{StageLocation: "~", PathOnStage: tmpJavaFunction2.JarName}, + ). + WithPackages("com.snowflake:snowpark:1.14.0", "com.snowflake:telemetry:0.1.0"). + WithExternalAccessIntegrations(externalAccessIntegration). + WithSecrets(map[string]sdk.SchemaObjectIdentifier{ + "def": secretId2, + }). + WithTargetPathParts(stage.ID().FullyQualifiedName(), jarName). + WithRuntimeVersion("11"). + WithComment("some other comment") resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, @@ -198,25 +245,58 @@ func TestAcc_FunctionJava_InlineFull(t *testing.T) { PreCheck: func() { acc.TestAccPreCheck(t) }, CheckDestroy: acc.CheckDestroy(t, resources.FunctionJava), Steps: []resource.TestStep{ - // CREATE BASIC + // CREATE WITH ALL { Config: config.FromModels(t, functionModel), Check: assert.AssertThat(t, resourceassert.FunctionJavaResource(t, functionModel.ResourceReference()). HasNameString(id.Name()). HasIsSecureString(r.BooleanDefault). - HasCommentString(sdk.DefaultFunctionComment). - HasImportsLength(0). + HasImportsLength(2). HasRuntimeVersionString("11"). HasFunctionDefinitionString(definition). + HasCommentString("some comment"). HasFunctionLanguageString("JAVA"). HasFullyQualifiedNameString(id.FullyQualifiedName()), assert.Check(resource.TestCheckResourceAttr(functionModel.ResourceReference(), "target_path.0.stage_location", stage.ID().FullyQualifiedName())), assert.Check(resource.TestCheckResourceAttr(functionModel.ResourceReference(), "target_path.0.path_on_stage", jarName)), + assert.Check(resource.TestCheckResourceAttr(functionModel.ResourceReference(), "secrets.#", "2")), + assert.Check(resource.TestCheckResourceAttr(functionModel.ResourceReference(), "external_access_integrations.#", "2")), + assert.Check(resource.TestCheckResourceAttr(functionModel.ResourceReference(), "packages.#", "2")), resourceshowoutputassert.FunctionShowOutput(t, functionModel.ResourceReference()). HasIsSecure(false), ), }, + // UPDATE WITHOUT RECREATION + { + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(functionModelUpdateWithoutRecreation.ResourceReference(), plancheck.ResourceActionUpdate), + }, + }, + Config: config.FromModels(t, functionModelUpdateWithoutRecreation), + Check: assert.AssertThat(t, + resourceassert.FunctionJavaResource(t, functionModelUpdateWithoutRecreation.ResourceReference()). + HasNameString(id.Name()). + HasIsSecureString(r.BooleanDefault). + HasImportsLength(2). + HasRuntimeVersionString("11"). + HasFunctionDefinitionString(definition). + HasCommentString("some other comment"). + HasFunctionLanguageString("JAVA"). + HasFullyQualifiedNameString(id.FullyQualifiedName()), + assert.Check(resource.TestCheckResourceAttr(functionModelUpdateWithoutRecreation.ResourceReference(), "target_path.0.stage_location", stage.ID().FullyQualifiedName())), + assert.Check(resource.TestCheckResourceAttr(functionModelUpdateWithoutRecreation.ResourceReference(), "target_path.0.path_on_stage", jarName)), + assert.Check(resource.TestCheckResourceAttr(functionModelUpdateWithoutRecreation.ResourceReference(), "secrets.#", "1")), + assert.Check(resource.TestCheckResourceAttr(functionModelUpdateWithoutRecreation.ResourceReference(), "secrets.0.secret_variable_name", "def")), + assert.Check(resource.TestCheckResourceAttr(functionModelUpdateWithoutRecreation.ResourceReference(), "secrets.0.secret_id", secretId2.FullyQualifiedName())), + assert.Check(resource.TestCheckResourceAttr(functionModelUpdateWithoutRecreation.ResourceReference(), "external_access_integrations.#", "1")), + assert.Check(resource.TestCheckResourceAttr(functionModelUpdateWithoutRecreation.ResourceReference(), "external_access_integrations.0", externalAccessIntegration.Name())), + assert.Check(resource.TestCheckResourceAttr(functionModelUpdateWithoutRecreation.ResourceReference(), "packages.#", "2")), + resourceshowoutputassert.FunctionShowOutput(t, functionModelUpdateWithoutRecreation.ResourceReference()). + HasIsSecure(false), + ), + }, }, }) } diff --git a/pkg/resources/procedure_commons.go b/pkg/resources/procedure_commons.go index 759f44f878..addb4f3c30 100644 --- a/pkg/resources/procedure_commons.go +++ b/pkg/resources/procedure_commons.go @@ -256,9 +256,8 @@ func procedureBaseSchema() map[string]schema.Schema { ForceNew: true, }, "comment": { - Type: schema.TypeString, - Optional: true, - // TODO [SNOW-1348103]: handle dynamic comment - this is a workaround for now + Type: schema.TypeString, + Optional: true, Default: "user-defined procedure", Description: "Specifies a comment for the procedure.", }, @@ -307,7 +306,6 @@ func procedureBaseSchema() map[string]schema.Schema { ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), }, Optional: true, - ForceNew: true, Description: "The names of [external access integrations](https://docs.snowflake.com/en/sql-reference/sql/create-external-access-integration) needed in order for this procedure’s handler code to access external networks. An external access integration specifies [network rules](https://docs.snowflake.com/en/sql-reference/sql/create-network-rule) and [secrets](https://docs.snowflake.com/en/sql-reference/sql/create-secret) that specify external locations and credentials (if any) allowed for use by handler code when making requests of an external network, such as an external REST API.", }, "secrets": { @@ -486,6 +484,16 @@ func parseProcedureImportsCommon(d *schema.ResourceData) ([]sdk.ProcedureImportR return imports, nil } +func parseProceduresPackagesCommon(d *schema.ResourceData) ([]sdk.ProcedurePackageRequest, error) { + packages := make([]sdk.ProcedurePackageRequest, 0) + if v, ok := d.GetOk("packages"); ok { + for _, pkg := range v.(*schema.Set).List() { + packages = append(packages, *sdk.NewProcedurePackageRequest(pkg.(string))) + } + } + return packages, nil +} + func parseProcedureTargetPathCommon(d *schema.ResourceData) (string, error) { var tp string if v, ok := d.GetOk("target_path"); ok { diff --git a/pkg/resources/procedure_java.go b/pkg/resources/procedure_java.go index bc4f417144..04fcb0cf1a 100644 --- a/pkg/resources/procedure_java.go +++ b/pkg/resources/procedure_java.go @@ -62,8 +62,12 @@ func CreateContextProcedureJava(ctx context.Context, d *schema.ResourceData, met } handler := d.Get("handler").(string) runtimeVersion := d.Get("runtime_version").(string) - // TODO [this PR]: handle real packages - packages := []sdk.ProcedurePackageRequest{*sdk.NewProcedurePackageRequest("com.snowflake:snowpark:1.14.0")} + + packages, err := parseProceduresPackagesCommon(d) + if err != nil { + return diag.FromErr(err) + } + packages = append(packages, *sdk.NewProcedurePackageRequest(fmt.Sprintf(`%s%s`, sdk.JavaSnowparkPackageString, d.Get("snowpark_package").(string)))) argumentDataTypes := collections.Map(argumentRequests, func(r sdk.ProcedureArgumentRequest) datatypes.DataType { return r.ArgDataType }) id := sdk.NewSchemaObjectIdentifierWithArgumentsNormalized(database, sc, name, argumentDataTypes...) @@ -73,12 +77,10 @@ func CreateContextProcedureJava(ctx context.Context, d *schema.ResourceData, met errs := errors.Join( booleanStringAttributeCreateBuilder(d, "is_secure", request.WithSecure), attributeMappedValueCreateBuilder[string](d, "null_input_behavior", request.WithNullInputBehavior, sdk.ToNullInputBehavior), - // TODO [SNOW-1348103]: handle the rest of the attributes - // comment + stringAttributeCreateBuilder(d, "comment", request.WithComment), setProcedureImportsInBuilder(d, request.WithImports), - // packages - // external_access_integrations - // secrets + setExternalAccessIntegrationsInBuilder(d, request.WithExternalAccessIntegrations), + setSecretsInBuilder(d, request.WithSecrets), setProcedureTargetPathInBuilder(d, request.WithTargetPath), stringAttributeCreateBuilder(d, "procedure_definition", request.WithProcedureDefinitionWrapped), ) @@ -122,18 +124,18 @@ func ReadContextProcedureJava(ctx context.Context, d *schema.ResourceData, meta // TODO [SNOW-1348103]: handle setting state to value from config errs := errors.Join( - // TODO [SNOW-1348103]: set the rest of the fields // not reading is_secure on purpose (handled as external change to show output) readFunctionOrProcedureArguments(d, allProcedureDetails.procedureDetails.NormalizedArguments), d.Set("return_type", allProcedureDetails.procedureDetails.ReturnDataType.ToSql()), // not reading null_input_behavior on purpose (handled as external change to show output) setRequiredFromStringPtr(d, "runtime_version", allProcedureDetails.procedureDetails.RuntimeVersion), - // comment + d.Set("comment", allProcedureDetails.procedure.Description), readFunctionOrProcedureImports(d, allProcedureDetails.procedureDetails.NormalizedImports), - // packages + d.Set("packages", allProcedureDetails.procedureDetails.NormalizedPackages), + d.Set("snowpark_package", allProcedureDetails.procedureDetails.SnowparkVersion), setRequiredFromStringPtr(d, "handler", allProcedureDetails.procedureDetails.Handler), - // external_access_integrations - // secrets + readFunctionOrProcedureExternalAccessIntegrations(d, allProcedureDetails.procedureDetails.NormalizedExternalAccessIntegrations), + readFunctionOrProcedureSecrets(d, allProcedureDetails.procedureDetails.NormalizedSecrets), readFunctionOrProcedureTargetPath(d, allProcedureDetails.procedureDetails.NormalizedTargetPath), setOptionalFromStringPtr(d, "procedure_definition", allProcedureDetails.procedureDetails.Body), d.Set("procedure_language", allProcedureDetails.procedureDetails.Language), @@ -173,11 +175,32 @@ func UpdateContextProcedureJava(ctx context.Context, d *schema.ResourceData, met setRequest := sdk.NewProcedureSetRequest() unsetRequest := sdk.NewProcedureUnsetRequest() - // TODO [SNOW-1348103]: handle all updates - // secure - // external access integration - // secrets - // comment + err = errors.Join( + stringAttributeUpdate(d, "comment", &setRequest.Comment, &unsetRequest.Comment), + func() error { + if d.HasChange("secrets") { + return setSecretsInBuilder(d, func(references []sdk.SecretReference) *sdk.ProcedureSetRequest { + return setRequest.WithSecretsList(sdk.SecretsListRequest{SecretsList: references}) + }) + } + return nil + }(), + func() error { + if d.HasChange("external_access_integrations") { + return setExternalAccessIntegrationsInBuilder(d, func(references []sdk.AccountObjectIdentifier) any { + if len(references) == 0 { + return unsetRequest.WithExternalAccessIntegrations(true) + } else { + return setRequest.WithExternalAccessIntegrations(references) + } + }) + } + return nil + }(), + ) + if err != nil { + return diag.FromErr(err) + } if updateParamDiags := handleProcedureParametersUpdate(d, setRequest, unsetRequest); len(updateParamDiags) > 0 { return updateParamDiags diff --git a/pkg/resources/procedure_java_acceptance_test.go b/pkg/resources/procedure_java_acceptance_test.go index 35bdd401ec..c6d0aba743 100644 --- a/pkg/resources/procedure_java_acceptance_test.go +++ b/pkg/resources/procedure_java_acceptance_test.go @@ -172,6 +172,27 @@ func TestAcc_ProcedureJava_InlineFull(t *testing.T) { stage, stageCleanup := acc.TestClient().Stage.CreateStage(t) t.Cleanup(stageCleanup) + secretId := acc.TestClient().Ids.RandomSchemaObjectIdentifier() + secretId2 := acc.TestClient().Ids.RandomSchemaObjectIdentifier() + + networkRule, networkRuleCleanup := acc.TestClient().NetworkRule.Create(t) + t.Cleanup(networkRuleCleanup) + + secret, secretCleanup := acc.TestClient().Secret.CreateWithGenericString(t, secretId, "test_secret_string") + t.Cleanup(secretCleanup) + + secret2, secret2Cleanup := acc.TestClient().Secret.CreateWithGenericString(t, secretId2, "test_secret_string_2") + t.Cleanup(secret2Cleanup) + + externalAccessIntegration, externalAccessIntegrationCleanup := acc.TestClient().ExternalAccessIntegration.CreateExternalAccessIntegrationWithNetworkRuleAndSecret(t, networkRule.ID(), secret.ID()) + t.Cleanup(externalAccessIntegrationCleanup) + + externalAccessIntegration2, externalAccessIntegration2Cleanup := acc.TestClient().ExternalAccessIntegration.CreateExternalAccessIntegrationWithNetworkRuleAndSecret(t, networkRule.ID(), secret2.ID()) + t.Cleanup(externalAccessIntegration2Cleanup) + + tmpJavaProcedure := acc.TestClient().CreateSampleJavaProcedureAndJarOnUserStage(t) + tmpJavaProcedure2 := acc.TestClient().CreateSampleJavaProcedureAndJarOnUserStage(t) + className := "TestFunc" funcName := "echoVarchar" argName := "x" @@ -186,8 +207,36 @@ func TestAcc_ProcedureJava_InlineFull(t *testing.T) { procedureModel := model.ProcedureJavaBasicInline("w", id, dataType, handler, definition). WithArgument(argName, dataType). + WithImports( + sdk.NormalizedPath{StageLocation: "~", PathOnStage: tmpJavaProcedure.JarName}, + sdk.NormalizedPath{StageLocation: "~", PathOnStage: tmpJavaProcedure2.JarName}, + ). + WithSnowparkPackage("1.14.0"). + WithPackages("com.snowflake:telemetry:0.1.0"). + WithExternalAccessIntegrations(externalAccessIntegration, externalAccessIntegration2). + WithSecrets(map[string]sdk.SchemaObjectIdentifier{ + "abc": secretId, + "def": secretId2, + }). + WithTargetPathParts(stage.ID().FullyQualifiedName(), jarName). + WithRuntimeVersion("11"). + WithComment("some comment") + + procedureModelUpdateWithoutRecreation := model.ProcedureJavaBasicInline("w", id, dataType, handler, definition). + WithArgument(argName, dataType). + WithImports( + sdk.NormalizedPath{StageLocation: "~", PathOnStage: tmpJavaProcedure.JarName}, + sdk.NormalizedPath{StageLocation: "~", PathOnStage: tmpJavaProcedure2.JarName}, + ). + WithSnowparkPackage("1.14.0"). + WithPackages("com.snowflake:telemetry:0.1.0"). + WithExternalAccessIntegrations(externalAccessIntegration). + WithSecrets(map[string]sdk.SchemaObjectIdentifier{ + "def": secretId2, + }). WithTargetPathParts(stage.ID().FullyQualifiedName(), jarName). - WithRuntimeVersion("11") + WithRuntimeVersion("11"). + WithComment("some other comment") resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, @@ -204,18 +253,52 @@ func TestAcc_ProcedureJava_InlineFull(t *testing.T) { resourceassert.ProcedureJavaResource(t, procedureModel.ResourceReference()). HasNameString(id.Name()). HasIsSecureString(r.BooleanDefault). - HasCommentString(sdk.DefaultProcedureComment). - HasImportsLength(0). + HasImportsLength(2). HasRuntimeVersionString("11"). HasProcedureDefinitionString(definition). + HasCommentString("some comment"). HasProcedureLanguageString("JAVA"). HasFullyQualifiedNameString(id.FullyQualifiedName()), assert.Check(resource.TestCheckResourceAttr(procedureModel.ResourceReference(), "target_path.0.stage_location", stage.ID().FullyQualifiedName())), assert.Check(resource.TestCheckResourceAttr(procedureModel.ResourceReference(), "target_path.0.path_on_stage", jarName)), + assert.Check(resource.TestCheckResourceAttr(procedureModel.ResourceReference(), "secrets.#", "2")), + assert.Check(resource.TestCheckResourceAttr(procedureModel.ResourceReference(), "external_access_integrations.#", "2")), + assert.Check(resource.TestCheckResourceAttr(procedureModel.ResourceReference(), "packages.#", "1")), + assert.Check(resource.TestCheckResourceAttr(procedureModel.ResourceReference(), "packages.0", "com.snowflake:telemetry:0.1.0")), resourceshowoutputassert.ProcedureShowOutput(t, procedureModel.ResourceReference()). HasIsSecure(false), ), }, + // UPDATE WITHOUT RECREATION + { + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(procedureModelUpdateWithoutRecreation.ResourceReference(), plancheck.ResourceActionUpdate), + }, + }, + Config: config.FromModels(t, procedureModelUpdateWithoutRecreation), + Check: assert.AssertThat(t, + resourceassert.ProcedureJavaResource(t, procedureModelUpdateWithoutRecreation.ResourceReference()). + HasNameString(id.Name()). + HasIsSecureString(r.BooleanDefault). + HasImportsLength(2). + HasRuntimeVersionString("11"). + HasProcedureDefinitionString(definition). + HasCommentString("some other comment"). + HasProcedureLanguageString("JAVA"). + HasFullyQualifiedNameString(id.FullyQualifiedName()), + assert.Check(resource.TestCheckResourceAttr(procedureModelUpdateWithoutRecreation.ResourceReference(), "target_path.0.stage_location", stage.ID().FullyQualifiedName())), + assert.Check(resource.TestCheckResourceAttr(procedureModelUpdateWithoutRecreation.ResourceReference(), "target_path.0.path_on_stage", jarName)), + assert.Check(resource.TestCheckResourceAttr(procedureModelUpdateWithoutRecreation.ResourceReference(), "secrets.#", "1")), + assert.Check(resource.TestCheckResourceAttr(procedureModelUpdateWithoutRecreation.ResourceReference(), "secrets.0.secret_variable_name", "def")), + assert.Check(resource.TestCheckResourceAttr(procedureModelUpdateWithoutRecreation.ResourceReference(), "secrets.0.secret_id", secretId2.FullyQualifiedName())), + assert.Check(resource.TestCheckResourceAttr(procedureModelUpdateWithoutRecreation.ResourceReference(), "external_access_integrations.#", "1")), + assert.Check(resource.TestCheckResourceAttr(procedureModelUpdateWithoutRecreation.ResourceReference(), "external_access_integrations.0", externalAccessIntegration.Name())), + assert.Check(resource.TestCheckResourceAttr(procedureModelUpdateWithoutRecreation.ResourceReference(), "packages.#", "1")), + resourceshowoutputassert.ProcedureShowOutput(t, procedureModelUpdateWithoutRecreation.ResourceReference()). + HasIsSecure(false), + ), + }, }, }) } diff --git a/pkg/sdk/functions_and_procedures_commons.go b/pkg/sdk/functions_and_procedures_commons.go index df64aba187..35f4ab3dbb 100644 --- a/pkg/sdk/functions_and_procedures_commons.go +++ b/pkg/sdk/functions_and_procedures_commons.go @@ -1,10 +1,12 @@ package sdk import ( + "encoding/json" "fmt" "log" "strings" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/datatypes" ) @@ -116,3 +118,34 @@ func parseFunctionOrProcedureArgument(arg string) (*NormalizedArgument, error) { } return &NormalizedArgument{argName, dt}, nil } + +// TODO [SNOW-1850370]: is this combo enough? - e.g. whitespace looks to be not trimmed +func parseFunctionOrProcedureExternalAccessIntegrations(raw string) ([]AccountObjectIdentifier, error) { + log.Printf("[DEBUG] external access integrations: %s", raw) + return collections.MapErr(ParseCommaSeparatedStringArray(raw, false), ParseAccountObjectIdentifier) +} + +// TODO [before V1]: test +func parseFunctionOrProcedurePackages(raw string) ([]string, error) { + log.Printf("[DEBUG] external access integrations: %s", raw) + return collections.Map(ParseCommaSeparatedStringArray(raw, true), strings.TrimSpace), nil +} + +// TODO [before V1]: unit test +func parseFunctionOrProcedureSecrets(raw string) (map[string]SchemaObjectIdentifier, error) { + log.Printf("[DEBUG] parsing secrets: %s", raw) + secrets := make(map[string]string) + err := json.Unmarshal([]byte(raw), &secrets) + if err != nil { + return nil, fmt.Errorf("could not parse secrets from Snowflake: %s, err: %w", raw, err) + } + normalizedSecrets := make(map[string]SchemaObjectIdentifier) + for k, v := range secrets { + id, err := ParseSchemaObjectIdentifier(v) + if err != nil { + return nil, fmt.Errorf("could not parse secrets from Snowflake: %s, err: %w", raw, err) + } + normalizedSecrets[k] = id + } + return normalizedSecrets, nil +} diff --git a/pkg/sdk/functions_ext.go b/pkg/sdk/functions_ext.go index facd9ede1d..93ae4ffa8e 100644 --- a/pkg/sdk/functions_ext.go +++ b/pkg/sdk/functions_ext.go @@ -33,11 +33,14 @@ type FunctionDetails struct { InstalledPackages *string // list present for python (hidden when SECURE) IsAggregate *bool // present for python - NormalizedImports []NormalizedPath - NormalizedTargetPath *NormalizedPath - ReturnDataType datatypes.DataType - ReturnNotNull bool - NormalizedArguments []NormalizedArgument + NormalizedImports []NormalizedPath + NormalizedTargetPath *NormalizedPath + ReturnDataType datatypes.DataType + ReturnNotNull bool + NormalizedArguments []NormalizedArgument + NormalizedExternalAccessIntegrations []AccountObjectIdentifier + NormalizedSecrets map[string]SchemaObjectIdentifier + NormalizedPackages []string } func functionDetailsFromRows(rows []FunctionDetail) (*FunctionDetails, error) { @@ -108,6 +111,36 @@ func functionDetailsFromRows(rows []FunctionDetail) (*FunctionDetails, error) { v.NormalizedArguments = args } + if v.ExternalAccessIntegrations != nil { + if p, err := parseFunctionOrProcedureExternalAccessIntegrations(*v.ExternalAccessIntegrations); err != nil { + errs = append(errs, err) + } else { + v.NormalizedExternalAccessIntegrations = p + } + } else { + v.NormalizedExternalAccessIntegrations = []AccountObjectIdentifier{} + } + + if v.Secrets != nil { + if p, err := parseFunctionOrProcedureSecrets(*v.Secrets); err != nil { + errs = append(errs, err) + } else { + v.NormalizedSecrets = p + } + } else { + v.NormalizedSecrets = map[string]SchemaObjectIdentifier{} + } + + if v.Packages != nil { + if p, err := parseFunctionOrProcedurePackages(*v.Packages); err != nil { + errs = append(errs, err) + } else { + v.NormalizedPackages = p + } + } else { + v.NormalizedPackages = []string{} + } + return v, errors.Join(errs...) } diff --git a/pkg/sdk/procedures_ext.go b/pkg/sdk/procedures_ext.go index de40fd8732..4a308dc97c 100644 --- a/pkg/sdk/procedures_ext.go +++ b/pkg/sdk/procedures_ext.go @@ -5,11 +5,16 @@ import ( "errors" "fmt" "strconv" + "strings" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/datatypes" ) -const DefaultProcedureComment = "user-defined procedure" +const ( + DefaultProcedureComment = "user-defined procedure" + JavaSnowparkPackageString = "com.snowflake:snowpark:" + PythonSnowparkPackageString = "snowflake-snowpark-python==" +) func (v *Procedure) ID() SchemaObjectIdentifierWithArguments { return NewSchemaObjectIdentifierWithArguments(v.CatalogName, v.SchemaName, v.Name, v.ArgumentsOld...) @@ -33,11 +38,16 @@ type ProcedureDetails struct { InstalledPackages *string // list present for python (hidden when SECURE) ExecuteAs string // present for all procedure types - NormalizedImports []NormalizedPath - NormalizedTargetPath *NormalizedPath - ReturnDataType datatypes.DataType - ReturnNotNull bool - NormalizedArguments []NormalizedArgument + NormalizedImports []NormalizedPath + NormalizedTargetPath *NormalizedPath + ReturnDataType datatypes.DataType + ReturnNotNull bool + NormalizedArguments []NormalizedArgument + NormalizedExternalAccessIntegrations []AccountObjectIdentifier + NormalizedSecrets map[string]SchemaObjectIdentifier + // NormalizedPackages does not contain a snowpark package - it is extracted only as a version in SnowparkVersion below + NormalizedPackages []string + SnowparkVersion string } func procedureDetailsFromRows(rows []ProcedureDetail) (*ProcedureDetails, error) { @@ -108,6 +118,75 @@ func procedureDetailsFromRows(rows []ProcedureDetail) (*ProcedureDetails, error) v.NormalizedArguments = args } + if v.ExternalAccessIntegrations != nil { + if p, err := parseFunctionOrProcedureExternalAccessIntegrations(*v.ExternalAccessIntegrations); err != nil { + errs = append(errs, err) + } else { + v.NormalizedExternalAccessIntegrations = p + } + } else { + v.NormalizedExternalAccessIntegrations = []AccountObjectIdentifier{} + } + + if v.Secrets != nil { + if p, err := parseFunctionOrProcedureSecrets(*v.Secrets); err != nil { + errs = append(errs, err) + } else { + v.NormalizedSecrets = p + } + } else { + v.NormalizedSecrets = map[string]SchemaObjectIdentifier{} + } + + if v.Packages != nil { + if p, err := parseFunctionOrProcedurePackages(*v.Packages); err != nil { + errs = append(errs, err) + } else { + // TODO [SNOW-1850370]: merge these and unit test + switch strings.ToUpper(v.Language) { + case "JAVA", "SCALA": + filtered := make([]string, 0) + var found bool + for _, o := range p { + o := strings.TrimSpace(o) + if strings.HasPrefix(o, JavaSnowparkPackageString) { + v.SnowparkVersion = strings.TrimPrefix(o, JavaSnowparkPackageString) + found = true + } else { + filtered = append(filtered, o) + } + } + v.NormalizedPackages = filtered + if !found { + errs = append(errs, fmt.Errorf("could not parse package from Snowflake, expected at least snowpark package, got %v", filtered)) + } + case "PYTHON": + filtered := make([]string, 0) + var found bool + for _, o := range p { + o := strings.TrimSpace(o) + if strings.HasPrefix(o, PythonSnowparkPackageString) { + v.SnowparkVersion = strings.TrimPrefix(o, PythonSnowparkPackageString) + found = true + } else { + filtered = append(filtered, o) + } + } + v.NormalizedPackages = filtered + if !found { + errs = append(errs, fmt.Errorf("could not parse package from Snowflake, expected at least snowpark package, got %v", filtered)) + } + } + } + } else { + switch strings.ToUpper(v.Language) { + case "JAVA", "SCALA", "PYTHON": + errs = append(errs, fmt.Errorf("could not parse package from Snowflake, expected at least snowpark package, got nil")) + default: + v.NormalizedPackages = []string{} + } + } + return v, errors.Join(errs...) } diff --git a/pkg/sdk/testint/functions_integration_test.go b/pkg/sdk/testint/functions_integration_test.go index 022ba7592a..8aa1b217fb 100644 --- a/pkg/sdk/testint/functions_integration_test.go +++ b/pkg/sdk/testint/functions_integration_test.go @@ -34,6 +34,7 @@ import ( // TODO [SNOW-1348103]: test secure // TODO [SNOW-1348103]: python aggregate func (100357 (P0000): Could not find accumulate method in function CVVEMHIT_06547800_08D6_DBCA_1AC7_5E422AFF8B39 with handler dump) // TODO [SNOW-1348103]: add test with multiple imports +// TODO [this PR]: test with multiple external access integrations and secrets func TestInt_Functions(t *testing.T) { client := testClient(t) ctx := context.Background() @@ -119,12 +120,14 @@ func TestInt_Functions(t *testing.T) { HasNullHandling(string(sdk.NullInputBehaviorCalledOnNullInput)). HasVolatility(string(sdk.ReturnResultsBehaviorVolatile)). HasExternalAccessIntegrationsNil(). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(). HasSecretsNil(). HasImports(`[]`). HasExactlyImportsNormalizedInAnyOrder(). HasHandler(handler). HasRuntimeVersionNil(). HasPackages(`[]`). + HasExactlyPackagesInAnyOrder(). HasTargetPathNil(). HasNormalizedTargetPathNil(). HasInstalledPackagesNil(). @@ -217,9 +220,8 @@ func TestInt_Functions(t *testing.T) { HasNullHandling(string(sdk.NullInputBehaviorReturnsNullInput)). HasVolatility(string(sdk.ReturnResultsBehaviorImmutable)). HasExactlyExternalAccessIntegrations(externalAccessIntegration). - // TODO [SNOW-1348103]: parse to identifier list - // TODO [SNOW-1348103]: check multiple secrets (to know how to parse) HasExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). + ContainsExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). HasImports(fmt.Sprintf(`[%s]`, tmpJavaFunction.JarLocation())). HasExactlyImportsNormalizedInAnyOrder(sdk.NormalizedPath{ StageLocation: "~", PathOnStage: tmpJavaFunction.JarName, @@ -227,6 +229,7 @@ func TestInt_Functions(t *testing.T) { HasHandler(handler). HasRuntimeVersion("11"). HasPackages(`[com.snowflake:snowpark:1.14.0,com.snowflake:telemetry:0.1.0]`). + HasExactlyPackagesInAnyOrder("com.snowflake:snowpark:1.14.0", "com.snowflake:telemetry:0.1.0"). HasTargetPath(targetPath). HasNormalizedTargetPath("~", jarName). HasInstalledPackagesNil(). @@ -295,6 +298,7 @@ func TestInt_Functions(t *testing.T) { HasNullHandling(string(sdk.NullInputBehaviorCalledOnNullInput)). HasVolatility(string(sdk.ReturnResultsBehaviorVolatile)). HasExternalAccessIntegrationsNil(). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(). HasSecretsNil(). HasImports(fmt.Sprintf(`[%s]`, importPath)). HasExactlyImportsNormalizedInAnyOrder(sdk.NormalizedPath{ @@ -303,6 +307,7 @@ func TestInt_Functions(t *testing.T) { HasHandler(handler). HasRuntimeVersionNil(). HasPackages(`[]`). + HasExactlyPackagesInAnyOrder(). HasTargetPathNil(). HasNormalizedTargetPathNil(). HasInstalledPackagesNil(). @@ -383,7 +388,9 @@ func TestInt_Functions(t *testing.T) { HasNullHandling(string(sdk.NullInputBehaviorReturnsNullInput)). HasVolatility(string(sdk.ReturnResultsBehaviorImmutable)). HasExactlyExternalAccessIntegrations(externalAccessIntegration). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(externalAccessIntegration). HasExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). + ContainsExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). HasImports(fmt.Sprintf(`[%s]`, tmpJavaFunction.JarLocation())). HasExactlyImportsNormalizedInAnyOrder(sdk.NormalizedPath{ StageLocation: "~", PathOnStage: tmpJavaFunction.JarName, @@ -391,6 +398,7 @@ func TestInt_Functions(t *testing.T) { HasHandler(handler). HasRuntimeVersion("11"). HasPackages(`[com.snowflake:snowpark:1.14.0,com.snowflake:telemetry:0.1.0]`). + HasExactlyPackagesInAnyOrder("com.snowflake:snowpark:1.14.0", "com.snowflake:telemetry:0.1.0"). HasTargetPathNil(). HasNormalizedTargetPathNil(). HasInstalledPackagesNil(). @@ -529,12 +537,14 @@ func TestInt_Functions(t *testing.T) { HasNullHandling(string(sdk.NullInputBehaviorCalledOnNullInput)). HasVolatility(string(sdk.ReturnResultsBehaviorVolatile)). HasExternalAccessIntegrationsNil(). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(). HasSecretsNil(). HasImportsNil(). HasExactlyImportsNormalizedInAnyOrder(). HasHandlerNil(). HasRuntimeVersionNil(). HasPackagesNil(). + HasExactlyPackagesInAnyOrder(). HasTargetPathNil(). HasNormalizedTargetPathNil(). HasInstalledPackagesNil(). @@ -606,12 +616,14 @@ func TestInt_Functions(t *testing.T) { HasNullHandling(string(sdk.NullInputBehaviorReturnsNullInput)). HasVolatility(string(sdk.ReturnResultsBehaviorImmutable)). HasExternalAccessIntegrationsNil(). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(). HasSecretsNil(). HasImportsNil(). HasExactlyImportsNormalizedInAnyOrder(). HasHandlerNil(). HasRuntimeVersionNil(). HasPackagesNil(). + HasExactlyPackagesInAnyOrder(). HasTargetPathNil(). HasNormalizedTargetPathNil(). HasInstalledPackagesNil(). @@ -679,12 +691,14 @@ func TestInt_Functions(t *testing.T) { HasNullHandling(string(sdk.NullInputBehaviorCalledOnNullInput)). HasVolatility(string(sdk.ReturnResultsBehaviorVolatile)). HasExternalAccessIntegrationsNil(). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(). HasSecretsNil(). HasImports(`[]`). HasExactlyImportsNormalizedInAnyOrder(). HasHandler(funcName). HasRuntimeVersion("3.8"). HasPackages(`[]`). + HasExactlyPackagesInAnyOrder(). HasTargetPathNil(). HasNormalizedTargetPathNil(). HasInstalledPackagesNotEmpty(). @@ -765,7 +779,9 @@ func TestInt_Functions(t *testing.T) { HasNullHandling(string(sdk.NullInputBehaviorReturnsNullInput)). HasVolatility(string(sdk.ReturnResultsBehaviorImmutable)). HasExactlyExternalAccessIntegrations(externalAccessIntegration). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(externalAccessIntegration). HasExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). + ContainsExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). HasImports(fmt.Sprintf(`[%s]`, tmpPythonFunction.PythonModuleLocation())). HasExactlyImportsNormalizedInAnyOrder(sdk.NormalizedPath{ StageLocation: "~", PathOnStage: tmpPythonFunction.PythonFileName(), @@ -773,6 +789,7 @@ func TestInt_Functions(t *testing.T) { HasHandler(funcName). HasRuntimeVersion("3.8"). HasPackages(`['absl-py==0.10.0','about-time==4.2.1']`). + HasExactlyPackagesInAnyOrder("absl-py==0.10.0", "about-time==4.2.1"). HasTargetPathNil(). HasNormalizedTargetPathNil(). HasInstalledPackagesNotEmpty(). @@ -838,6 +855,7 @@ func TestInt_Functions(t *testing.T) { HasNullHandling(string(sdk.NullInputBehaviorCalledOnNullInput)). HasVolatility(string(sdk.ReturnResultsBehaviorVolatile)). HasExternalAccessIntegrationsNil(). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(). HasSecretsNil(). HasImports(fmt.Sprintf(`[%s]`, tmpPythonFunction.PythonModuleLocation())). HasExactlyImportsNormalizedInAnyOrder(sdk.NormalizedPath{ @@ -846,6 +864,7 @@ func TestInt_Functions(t *testing.T) { HasHandler(tmpPythonFunction.PythonHandler()). HasRuntimeVersion("3.8"). HasPackages(`[]`). + HasExactlyPackagesInAnyOrder(). HasTargetPathNil(). HasNormalizedTargetPathNil(). HasInstalledPackagesNotEmpty(). @@ -923,7 +942,9 @@ func TestInt_Functions(t *testing.T) { HasNullHandling(string(sdk.NullInputBehaviorReturnsNullInput)). HasVolatility(string(sdk.ReturnResultsBehaviorImmutable)). HasExactlyExternalAccessIntegrations(externalAccessIntegration). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(externalAccessIntegration). HasExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). + ContainsExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). HasImports(fmt.Sprintf(`[%s]`, tmpPythonFunction.PythonModuleLocation())). HasExactlyImportsNormalizedInAnyOrder(sdk.NormalizedPath{ StageLocation: "~", PathOnStage: tmpPythonFunction.PythonFileName(), @@ -931,6 +952,7 @@ func TestInt_Functions(t *testing.T) { HasHandler(tmpPythonFunction.PythonHandler()). HasRuntimeVersion("3.8"). HasPackages(`['absl-py==0.10.0','about-time==4.2.1']`). + HasExactlyPackagesInAnyOrder("about-time==4.2.1", "absl-py==0.10.0"). HasTargetPathNil(). HasNormalizedTargetPathNil(). HasInstalledPackagesNotEmpty(). @@ -999,12 +1021,14 @@ func TestInt_Functions(t *testing.T) { HasNullHandling(string(sdk.NullInputBehaviorCalledOnNullInput)). HasVolatility(string(sdk.ReturnResultsBehaviorVolatile)). HasExternalAccessIntegrationsNil(). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(). HasSecretsNil(). HasImports(`[]`). HasExactlyImportsNormalizedInAnyOrder(). HasHandler(handler). HasRuntimeVersion("2.12"). HasPackages(`[]`). + HasExactlyPackagesInAnyOrder(). HasTargetPathNil(). HasNormalizedTargetPathNil(). HasInstalledPackagesNil(). @@ -1094,7 +1118,9 @@ func TestInt_Functions(t *testing.T) { HasNullHandling(string(sdk.NullInputBehaviorReturnsNullInput)). HasVolatility(string(sdk.ReturnResultsBehaviorImmutable)). HasExactlyExternalAccessIntegrations(externalAccessIntegration). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(externalAccessIntegration). HasExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). + ContainsExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). HasImports(fmt.Sprintf(`[%s]`, tmpJavaFunction.JarLocation())). HasExactlyImportsNormalizedInAnyOrder(sdk.NormalizedPath{ StageLocation: "~", PathOnStage: tmpJavaFunction.JarName, @@ -1102,6 +1128,7 @@ func TestInt_Functions(t *testing.T) { HasHandler(handler). HasRuntimeVersion("2.12"). HasPackages(`[com.snowflake:snowpark:1.14.0,com.snowflake:telemetry:0.1.0]`). + HasExactlyPackagesInAnyOrder("com.snowflake:snowpark:1.14.0", "com.snowflake:telemetry:0.1.0"). HasTargetPath(targetPath). HasNormalizedTargetPath("~", jarName). HasInstalledPackagesNil(). @@ -1168,6 +1195,7 @@ func TestInt_Functions(t *testing.T) { HasNullHandling(string(sdk.NullInputBehaviorCalledOnNullInput)). HasVolatility(string(sdk.ReturnResultsBehaviorVolatile)). HasExternalAccessIntegrationsNil(). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(). HasSecretsNil(). HasImports(fmt.Sprintf(`[%s]`, importPath)). HasExactlyImportsNormalizedInAnyOrder(sdk.NormalizedPath{ @@ -1176,6 +1204,7 @@ func TestInt_Functions(t *testing.T) { HasHandler(handler). HasRuntimeVersion("2.12"). HasPackages(`[]`). + HasExactlyPackagesInAnyOrder(). HasTargetPathNil(). HasNormalizedTargetPathNil(). HasInstalledPackagesNil(). @@ -1253,7 +1282,9 @@ func TestInt_Functions(t *testing.T) { HasNullHandling(string(sdk.NullInputBehaviorReturnsNullInput)). HasVolatility(string(sdk.ReturnResultsBehaviorImmutable)). HasExactlyExternalAccessIntegrations(externalAccessIntegration). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(externalAccessIntegration). HasExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). + ContainsExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). HasImports(fmt.Sprintf(`[%s]`, tmpJavaFunction.JarLocation())). HasExactlyImportsNormalizedInAnyOrder(sdk.NormalizedPath{ StageLocation: "~", PathOnStage: tmpJavaFunction.JarName, @@ -1261,6 +1292,7 @@ func TestInt_Functions(t *testing.T) { HasHandler(handler). HasRuntimeVersion("2.12"). HasPackages(`[com.snowflake:snowpark:1.14.0,com.snowflake:telemetry:0.1.0]`). + HasExactlyPackagesInAnyOrder("com.snowflake:snowpark:1.14.0", "com.snowflake:telemetry:0.1.0"). HasTargetPathNil(). HasNormalizedTargetPathNil(). HasInstalledPackagesNil(). @@ -1326,12 +1358,14 @@ func TestInt_Functions(t *testing.T) { HasNullHandlingNil(). HasVolatilityNil(). HasExternalAccessIntegrationsNil(). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(). HasSecretsNil(). HasImportsNil(). HasExactlyImportsNormalizedInAnyOrder(). HasHandlerNil(). HasRuntimeVersionNil(). HasPackagesNil(). + HasExactlyPackagesInAnyOrder(). HasTargetPathNil(). HasNormalizedTargetPathNil(). HasInstalledPackagesNil(). @@ -1434,12 +1468,14 @@ func TestInt_Functions(t *testing.T) { // HasVolatility(string(sdk.ReturnResultsBehaviorImmutable)). HasVolatilityNil(). HasExternalAccessIntegrationsNil(). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(). HasSecretsNil(). HasImportsNil(). HasExactlyImportsNormalizedInAnyOrder(). HasHandlerNil(). HasRuntimeVersionNil(). HasPackagesNil(). + HasExactlyPackagesInAnyOrder(). HasTargetPathNil(). HasNormalizedTargetPathNil(). HasInstalledPackagesNil(). @@ -1502,12 +1538,14 @@ func TestInt_Functions(t *testing.T) { HasNullHandlingNil(). HasVolatilityNil(). HasExternalAccessIntegrationsNil(). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(). HasSecretsNil(). HasImportsNil(). HasExactlyImportsNormalizedInAnyOrder(). HasHandlerNil(). HasRuntimeVersionNil(). HasPackagesNil(). + HasExactlyPackagesInAnyOrder(). HasTargetPathNil(). HasNormalizedTargetPathNil(). HasInstalledPackagesNil(). @@ -1581,6 +1619,7 @@ func TestInt_Functions(t *testing.T) { assertions.AssertThatObject(t, objectassert.FunctionDetails(t, id). HasExternalAccessIntegrationsNil(). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(). HasSecretsNil(), ) @@ -1609,7 +1648,9 @@ func TestInt_Functions(t *testing.T) { assertions.AssertThatObject(t, objectassert.FunctionDetails(t, id). HasExactlyExternalAccessIntegrations(externalAccessIntegration). - HasExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}), + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(externalAccessIntegration). + HasExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). + ContainsExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}), ) assertParametersSet(t, objectparametersassert.FunctionParameters(t, id)) @@ -1636,6 +1677,7 @@ func TestInt_Functions(t *testing.T) { assertions.AssertThatObject(t, objectassert.FunctionDetails(t, id). HasExternalAccessIntegrationsNil(). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(). // TODO [SNOW-1850370]: apparently UNSET external access integrations cleans out secrets in the describe but leaves it in SHOW HasSecretsNil(), ) diff --git a/pkg/sdk/testint/procedures_integration_test.go b/pkg/sdk/testint/procedures_integration_test.go index c5434d6308..e8b54a9a4d 100644 --- a/pkg/sdk/testint/procedures_integration_test.go +++ b/pkg/sdk/testint/procedures_integration_test.go @@ -107,12 +107,15 @@ func TestInt_Procedures(t *testing.T) { HasNullHandling(string(sdk.NullInputBehaviorCalledOnNullInput)). HasVolatility(string(sdk.ReturnResultsBehaviorVolatile)). HasExternalAccessIntegrationsNil(). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(). HasSecretsNil(). HasImports(`[]`). HasExactlyImportsNormalizedInAnyOrder(). HasHandler(handler). HasRuntimeVersion("11"). HasPackages(`[com.snowflake:snowpark:1.14.0]`). + HasExactlyPackagesInAnyOrder(). + HasSnowparkVersion("1.14.0"). HasTargetPathNil(). HasNormalizedTargetPathNil(). HasInstalledPackagesNil(). @@ -197,7 +200,9 @@ func TestInt_Procedures(t *testing.T) { HasNullHandling(string(sdk.NullInputBehaviorReturnsNullInput)). HasVolatility(string(sdk.ReturnResultsBehaviorImmutable)). HasExactlyExternalAccessIntegrations(externalAccessIntegration). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(externalAccessIntegration). HasExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). + ContainsExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). HasImports(fmt.Sprintf(`[%s]`, tmpJavaProcedure.JarLocation())). HasExactlyImportsNormalizedInAnyOrder(sdk.NormalizedPath{ StageLocation: "~", PathOnStage: tmpJavaProcedure.JarName, @@ -205,6 +210,8 @@ func TestInt_Procedures(t *testing.T) { HasHandler(handler). HasRuntimeVersion("11"). HasPackages(`[com.snowflake:snowpark:1.14.0,com.snowflake:telemetry:0.1.0]`). + HasExactlyPackagesInAnyOrder("com.snowflake:telemetry:0.1.0"). + HasSnowparkVersion("1.14.0"). HasTargetPath(targetPath). HasNormalizedTargetPath("~", jarName). HasInstalledPackagesNil(). @@ -273,6 +280,7 @@ func TestInt_Procedures(t *testing.T) { HasNullHandling(string(sdk.NullInputBehaviorCalledOnNullInput)). HasVolatility(string(sdk.ReturnResultsBehaviorVolatile)). HasExternalAccessIntegrationsNil(). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(). HasSecretsNil(). HasImports(fmt.Sprintf(`[%s]`, importPath)). HasExactlyImportsNormalizedInAnyOrder(sdk.NormalizedPath{ @@ -281,6 +289,8 @@ func TestInt_Procedures(t *testing.T) { HasHandler(handler). HasRuntimeVersion("11"). HasPackages(`[com.snowflake:snowpark:1.14.0,com.snowflake:telemetry:0.1.0]`). + HasExactlyPackagesInAnyOrder("com.snowflake:telemetry:0.1.0"). + HasSnowparkVersion("1.14.0"). HasTargetPathNil(). HasNormalizedTargetPathNil(). HasInstalledPackagesNil(). @@ -356,7 +366,9 @@ func TestInt_Procedures(t *testing.T) { HasNullHandling(string(sdk.NullInputBehaviorReturnsNullInput)). HasVolatility(string(sdk.ReturnResultsBehaviorImmutable)). HasExactlyExternalAccessIntegrations(externalAccessIntegration). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(externalAccessIntegration). HasExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). + ContainsExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). HasImports(fmt.Sprintf(`[%s]`, tmpJavaProcedure.JarLocation())). HasExactlyImportsNormalizedInAnyOrder(sdk.NormalizedPath{ StageLocation: "~", PathOnStage: tmpJavaProcedure.JarName, @@ -364,6 +376,8 @@ func TestInt_Procedures(t *testing.T) { HasHandler(handler). HasRuntimeVersion("11"). HasPackages(`[com.snowflake:snowpark:1.14.0,com.snowflake:telemetry:0.1.0]`). + HasExactlyPackagesInAnyOrder("com.snowflake:telemetry:0.1.0"). + HasSnowparkVersion("1.14.0"). HasTargetPathNil(). HasNormalizedTargetPathNil(). HasInstalledPackagesNil(). @@ -500,6 +514,7 @@ func TestInt_Procedures(t *testing.T) { HasNullHandling(string(sdk.NullInputBehaviorCalledOnNullInput)). HasVolatility(string(sdk.ReturnResultsBehaviorVolatile)). HasExternalAccessIntegrationsNil(). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(). HasSecretsNil(). HasImportsNil(). HasExactlyImportsNormalizedInAnyOrder(). @@ -572,6 +587,7 @@ func TestInt_Procedures(t *testing.T) { HasNullHandling(string(sdk.NullInputBehaviorReturnsNullInput)). HasVolatility(string(sdk.ReturnResultsBehaviorImmutable)). HasExternalAccessIntegrationsNil(). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(). HasSecretsNil(). HasImportsNil(). HasExactlyImportsNormalizedInAnyOrder(). @@ -644,12 +660,15 @@ func TestInt_Procedures(t *testing.T) { HasNullHandling(string(sdk.NullInputBehaviorCalledOnNullInput)). HasVolatility(string(sdk.ReturnResultsBehaviorVolatile)). HasExternalAccessIntegrationsNil(). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(). HasSecretsNil(). HasImports(`[]`). HasExactlyImportsNormalizedInAnyOrder(). HasHandler(funcName). HasRuntimeVersion("3.8"). HasPackages(`['snowflake-snowpark-python==1.14.0']`). + HasExactlyPackagesInAnyOrder(). + HasSnowparkVersion("1.14.0"). HasTargetPathNil(). HasNormalizedTargetPathNil(). HasInstalledPackagesNotEmpty(). @@ -728,7 +747,9 @@ func TestInt_Procedures(t *testing.T) { HasNullHandling(string(sdk.NullInputBehaviorReturnsNullInput)). HasVolatility(string(sdk.ReturnResultsBehaviorImmutable)). HasExactlyExternalAccessIntegrations(externalAccessIntegration). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(externalAccessIntegration). HasExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). + ContainsExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). HasImports(fmt.Sprintf(`[%s]`, tmpPythonFunction.PythonModuleLocation())). HasExactlyImportsNormalizedInAnyOrder(sdk.NormalizedPath{ StageLocation: "~", PathOnStage: tmpPythonFunction.PythonFileName(), @@ -736,6 +757,8 @@ func TestInt_Procedures(t *testing.T) { HasHandler(funcName). HasRuntimeVersion("3.8"). HasPackages(`['snowflake-snowpark-python==1.14.0','absl-py==0.10.0']`). + HasExactlyPackagesInAnyOrder("absl-py==0.10.0"). + HasSnowparkVersion("1.14.0"). HasTargetPathNil(). HasNormalizedTargetPathNil(). HasInstalledPackagesNotEmpty(). @@ -800,6 +823,7 @@ func TestInt_Procedures(t *testing.T) { HasNullHandling(string(sdk.NullInputBehaviorCalledOnNullInput)). HasVolatility(string(sdk.ReturnResultsBehaviorVolatile)). HasExternalAccessIntegrationsNil(). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(). HasSecretsNil(). HasImports(fmt.Sprintf(`[%s]`, tmpPythonFunction.PythonModuleLocation())). HasExactlyImportsNormalizedInAnyOrder(sdk.NormalizedPath{ @@ -808,6 +832,8 @@ func TestInt_Procedures(t *testing.T) { HasHandler(tmpPythonFunction.PythonHandler()). HasRuntimeVersion("3.8"). HasPackages(`['snowflake-snowpark-python==1.14.0']`). + HasExactlyPackagesInAnyOrder(). + HasSnowparkVersion("1.14.0"). HasTargetPathNil(). HasNormalizedTargetPathNil(). HasInstalledPackagesNotEmpty(). @@ -883,7 +909,9 @@ func TestInt_Procedures(t *testing.T) { HasNullHandling(string(sdk.NullInputBehaviorReturnsNullInput)). HasVolatility(string(sdk.ReturnResultsBehaviorImmutable)). HasExactlyExternalAccessIntegrations(externalAccessIntegration). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(externalAccessIntegration). HasExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). + ContainsExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). HasImports(fmt.Sprintf(`[%s]`, tmpPythonFunction.PythonModuleLocation())). HasExactlyImportsNormalizedInAnyOrder(sdk.NormalizedPath{ StageLocation: "~", PathOnStage: tmpPythonFunction.PythonFileName(), @@ -891,6 +919,8 @@ func TestInt_Procedures(t *testing.T) { HasHandler(tmpPythonFunction.PythonHandler()). HasRuntimeVersion("3.8"). HasPackages(`['snowflake-snowpark-python==1.14.0','absl-py==0.10.0']`). + HasExactlyPackagesInAnyOrder("absl-py==0.10.0"). + HasSnowparkVersion("1.14.0"). HasTargetPathNil(). HasNormalizedTargetPathNil(). HasInstalledPackagesNotEmpty(). @@ -959,12 +989,15 @@ func TestInt_Procedures(t *testing.T) { HasNullHandling(string(sdk.NullInputBehaviorCalledOnNullInput)). HasVolatility(string(sdk.ReturnResultsBehaviorVolatile)). HasExternalAccessIntegrationsNil(). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(). HasSecretsNil(). HasImports(`[]`). HasExactlyImportsNormalizedInAnyOrder(). HasHandler(handler). HasRuntimeVersion("2.12"). HasPackages(`[com.snowflake:snowpark:1.14.0]`). + HasExactlyPackagesInAnyOrder(). + HasSnowparkVersion("1.14.0"). HasTargetPathNil(). HasNormalizedTargetPathNil(). HasInstalledPackagesNil(). @@ -1050,7 +1083,9 @@ func TestInt_Procedures(t *testing.T) { HasNullHandling(string(sdk.NullInputBehaviorReturnsNullInput)). HasVolatility(string(sdk.ReturnResultsBehaviorImmutable)). HasExactlyExternalAccessIntegrations(externalAccessIntegration). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(externalAccessIntegration). HasExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). + ContainsExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). HasImports(fmt.Sprintf(`[%s]`, tmpJavaProcedure.JarLocation())). HasExactlyImportsNormalizedInAnyOrder(sdk.NormalizedPath{ StageLocation: "~", PathOnStage: tmpJavaProcedure.JarName, @@ -1058,6 +1093,8 @@ func TestInt_Procedures(t *testing.T) { HasHandler(handler). HasRuntimeVersion("2.12"). HasPackages(`[com.snowflake:snowpark:1.14.0,com.snowflake:telemetry:0.1.0]`). + HasExactlyPackagesInAnyOrder("com.snowflake:telemetry:0.1.0"). + HasSnowparkVersion("1.14.0"). HasTargetPath(targetPath). HasNormalizedTargetPath("~", jarName). HasInstalledPackagesNil(). @@ -1123,6 +1160,7 @@ func TestInt_Procedures(t *testing.T) { HasNullHandling(string(sdk.NullInputBehaviorCalledOnNullInput)). HasVolatility(string(sdk.ReturnResultsBehaviorVolatile)). HasExternalAccessIntegrationsNil(). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(). HasSecretsNil(). HasImports(fmt.Sprintf(`[%s]`, importPath)). HasExactlyImportsNormalizedInAnyOrder(sdk.NormalizedPath{ @@ -1131,6 +1169,8 @@ func TestInt_Procedures(t *testing.T) { HasHandler(handler). HasRuntimeVersion("2.12"). HasPackages(`[com.snowflake:snowpark:1.14.0]`). + HasExactlyPackagesInAnyOrder(). + HasSnowparkVersion("1.14.0"). HasTargetPathNil(). HasNormalizedTargetPathNil(). HasInstalledPackagesNil(). @@ -1208,7 +1248,9 @@ func TestInt_Procedures(t *testing.T) { HasNullHandling(string(sdk.NullInputBehaviorReturnsNullInput)). HasVolatility(string(sdk.ReturnResultsBehaviorImmutable)). HasExactlyExternalAccessIntegrations(externalAccessIntegration). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(externalAccessIntegration). HasExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). + ContainsExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). HasImports(fmt.Sprintf(`[%s]`, tmpJavaProcedure.JarLocation())). HasExactlyImportsNormalizedInAnyOrder(sdk.NormalizedPath{ StageLocation: "~", PathOnStage: tmpJavaProcedure.JarName, @@ -1216,6 +1258,8 @@ func TestInt_Procedures(t *testing.T) { HasHandler(handler). HasRuntimeVersion("2.12"). HasPackages(`[com.snowflake:snowpark:1.14.0,com.snowflake:telemetry:0.1.0]`). + HasExactlyPackagesInAnyOrder("com.snowflake:telemetry:0.1.0"). + HasSnowparkVersion("1.14.0"). HasTargetPathNil(). HasNormalizedTargetPathNil(). HasInstalledPackagesNil(). @@ -1277,6 +1321,7 @@ func TestInt_Procedures(t *testing.T) { HasNullHandlingNil(). HasVolatilityNil(). HasExternalAccessIntegrationsNil(). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(). HasSecretsNil(). HasImportsNil(). HasExactlyImportsNormalizedInAnyOrder(). @@ -1382,6 +1427,7 @@ func TestInt_Procedures(t *testing.T) { HasVolatilityNil(). HasVolatilityNil(). HasExternalAccessIntegrationsNil(). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(). HasSecretsNil(). HasImportsNil(). HasExactlyImportsNormalizedInAnyOrder(). @@ -1446,6 +1492,7 @@ func TestInt_Procedures(t *testing.T) { HasNullHandlingNil(). HasVolatilityNil(). HasExternalAccessIntegrationsNil(). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(). HasSecretsNil(). HasImportsNil(). HasExactlyImportsNormalizedInAnyOrder(). @@ -1828,6 +1875,7 @@ def filter_by_role(session, table_name, role): assertions.AssertThatObject(t, objectassert.ProcedureDetails(t, id). HasExternalAccessIntegrationsNil(). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(). HasSecretsNil(), ) @@ -1858,7 +1906,9 @@ def filter_by_role(session, table_name, role): assertions.AssertThatObject(t, objectassert.ProcedureDetails(t, id). HasExactlyExternalAccessIntegrations(externalAccessIntegration). - HasExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}), + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(externalAccessIntegration). + HasExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). + ContainsExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}), ) assertParametersSet(t, objectparametersassert.ProcedureParameters(t, id)) @@ -1886,6 +1936,7 @@ def filter_by_role(session, table_name, role): assertions.AssertThatObject(t, objectassert.ProcedureDetails(t, id). HasExternalAccessIntegrationsNil(). + HasExactlyExternalAccessIntegrationsNormalizedInAnyOrder(). // TODO [SNOW-1850370]: apparently UNSET external access integrations cleans out secrets in the describe but leaves it in SHOW HasSecretsNil(), )