Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ across different versions.

## v1.0.4 ➞ v1.0.5
Comment thread
sfc-gh-asawicki marked this conversation as resolved.

### Changes in TOML configuration file requirements
Before this version, it was possible to abuse the provider by providing a huge TOML config file which was read every time. To mitigate this, we set a limit of the supported file size to 10MB.

### Tracking external changes for oauth_redirect_uri in the snowflake_oauth_integration_for_partner_applications resource
From this version, the snowflake_oauth_integration_for_partner_applications resource is able to
detect changes on the Snowflake side and apply appropriate action from the provider level. This may produce
Expand Down
2 changes: 2 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,8 @@ password='password'
role='ACCOUNTADMIN'
```

--> **Note: TOML file size is limited to 10MB.

Not all fields must be configured in one source; users can choose which fields are configured in which source.
Provider uses an established hierarchy of sources. The current behavior is that for each field:
1. Check if it is present in the provider configuration. If yes, use this value. If not, go to step 2.
Expand Down
8 changes: 8 additions & 0 deletions pkg/acceptance/helpers/tmp_toml_setup_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ func (c *TestClient) TempIncorrectTomlConfigForLegacyServiceUser(t *testing.T, l
})
}

func (c *TestClient) TempTooBigTomlConfigForServiceUser(t *testing.T, serviceUser *TmpServiceUser) *TmpTomlConfig {
t.Helper()
return c.StoreTempTomlConfig(t, func(profile string) string {
c := make([]byte, 11*1024*1024)
return TomlConfigForServiceUser(t, profile, serviceUser.UserId, serviceUser.RoleId, serviceUser.WarehouseId, serviceUser.AccountId, string(c))
})
}

func (c *TestClient) StoreTempTomlConfig(t *testing.T, tomlProvider func(string) string) *TmpTomlConfig {
t.Helper()

Expand Down
5 changes: 4 additions & 1 deletion pkg/internal/logging/debug_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import (
"io"
"log"
"os"

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

// TODO (next PRs): remove extra logging
func init() {
additionalDebugLoggingEnabled = os.Getenv("SF_TF_ADDITIONAL_DEBUG_LOGGING") != ""
additionalDebugLoggingEnabled = oswrapper.Getenv("SF_TF_ADDITIONAL_DEBUG_LOGGING") != ""
DebugLogger = log.New(os.Stderr, "sf-tf-additional-debug ", log.LstdFlags|log.Lmsgprefix|log.LUTC|log.Lmicroseconds)
if !additionalDebugLoggingEnabled {
DebugLogger.SetOutput(io.Discard)
Expand Down
67 changes: 67 additions & 0 deletions pkg/internal/oswrapper/os.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Package oswrapper is a wrapper around the standard os package that allows more secure interactions with the operating system.
// It should be used as a replacement in production code of the standard os package.
package oswrapper

import (
"fmt"
"log"
"os"
"runtime"
)

const (
maxFileSizeInMb = 10
)

// IsRunningOnWindows returns true if the code is running on Windows.
func IsRunningOnWindows() bool {
return runtime.GOOS == "windows"
}

// Stat is an os.Stat wrapper.
func Stat(path string) (os.FileInfo, error) {
log.Printf("[DEBUG] reading the %s file info", path)
return os.Stat(path)
}

// Getenv is an os.Getenv wrapper.
func Getenv(name string) string {
log.Printf("[DEBUG] reading the %s environmental variable", name)
return os.Getenv(name)
}

// LookupEnv is an os.LookupEnv wrapper.
func LookupEnv(name string) (string, bool) {
log.Printf("[DEBUG] reading the %s environmental variable", name)
return os.LookupEnv(name)
}

// ReadFileSafe checks if a file is safe to read, and then reads it.
func ReadFileSafe(path string) ([]byte, error) {
if err := fileIsSafeToRead(path); err != nil {
return nil, err
}
return readFile(path)
}

func readFile(path string) ([]byte, error) {
log.Printf("[DEBUG] reading the %s file", path)
return os.ReadFile(path)
}

func fileIsSafeToRead(path string) error {
fileinfo, err := Stat(path)
if err != nil {
return fmt.Errorf("reading information about the config file: %w", err)
}
if fileinfo.Size() > maxFileSizeInMb*1024*1024 {
return fmt.Errorf("config file %s is too big - maximum allowed size is %dMB", path, maxFileSizeInMb)
}
return nil
}

// UserHomeDir is an os.UserHomeDir wrapper.
func UserHomeDir() (string, error) {
log.Printf("[DEBUG] reading the user home directory location from the operating system")
return os.UserHomeDir()
}
27 changes: 27 additions & 0 deletions pkg/internal/oswrapper/os_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package oswrapper_test

import (
"fmt"
"testing"

"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/oswrapper"
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/testhelpers"
"github.com/stretchr/testify/require"
)

func TestLoadConfigFileThatIsTooBig(t *testing.T) {
if oswrapper.IsRunningOnWindows() {
t.Skip("checking file sizes on Windows is currently done in manual tests package")
}
c := make([]byte, 11*1024*1024)
configPath := testhelpers.TestFile(t, "config", c)

_, err := oswrapper.ReadFileSafe(configPath)
require.ErrorContains(t, err, fmt.Sprintf("config file %s is too big - maximum allowed size is 10MB", configPath))
}

func TestLoadConfigFileThatDoesNotExist(t *testing.T) {
configPath := "non-existing"
_, err := oswrapper.ReadFileSafe(configPath)
require.ErrorContains(t, err, fmt.Sprintf("reading information about the config file: stat %s: no such file or directory", configPath))
}
1 change: 1 addition & 0 deletions pkg/manual_tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ Here's the list of cases we currently cannot reproduce and write acceptance test
- `user_default_database_and_role`: Setting up a user with default_namespace and default_role, then logging into that user to see what happens with those values in various scenarios (e.g. insufficient privileges on the role).
- `authentication_methods`: Some of the authentication methods require manual steps, like confirming MFA or setting more dependencies.
- `benchmarks`: Performance benchmarks require manually running `terraform` command to imitate the user workflow.
- `windows`: Test cases for the Windows platform. Potentially, these tests could be incorporated in a platform-specific testing workflows.
22 changes: 22 additions & 0 deletions pkg/manual_tests/windows/files_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package windows_test

import (
"fmt"
"testing"

"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/oswrapper"
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk"
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/testhelpers"
"github.com/stretchr/testify/require"
)

func TestLoadConfigFileThatIsTooBig(t *testing.T) {
if !oswrapper.IsRunningOnWindows() {
t.Skip("checking file sizes on other platforms is currently done in the sdk package")
}
c := make([]byte, 11*1024*1024)
configPath := testhelpers.TestFile(t, "config", c)

_, err := sdk.LoadConfigFile(configPath)
require.ErrorContains(t, err, fmt.Sprintf("config file %s is too big - maximum allowed size is 10MB", configPath))
}
10 changes: 5 additions & 5 deletions pkg/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import (
"fmt"
"net"
"net/url"
"os"
"strings"

"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/testenvs"
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/datasources"
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/oswrapper"
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider"
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider/docs"
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider/validators"
Expand Down Expand Up @@ -468,7 +468,7 @@ func getResources() map[string]*schema.Resource {
"snowflake_warehouse": resources.Warehouse(),
}

if os.Getenv(string(testenvs.EnableObjectRenamingTest)) != "" {
if oswrapper.Getenv(string(testenvs.EnableObjectRenamingTest)) != "" {
resourceList["snowflake_object_renaming"] = resources.ObjectRenamingListsAndSets()
}

Expand Down Expand Up @@ -532,7 +532,7 @@ var (

func ConfigureProvider(ctx context.Context, s *schema.ResourceData) (any, diag.Diagnostics) {
// hacky way to speed up our acceptance tests
if os.Getenv("TF_ACC") != "" && os.Getenv("SF_TF_ACC_TEST_CONFIGURE_CLIENT_ONCE") == "true" {
if oswrapper.Getenv("TF_ACC") != "" && oswrapper.Getenv("SF_TF_ACC_TEST_CONFIGURE_CLIENT_ONCE") == "true" {
if configureProviderCtx != nil {
return configureProviderCtx, nil
}
Expand Down Expand Up @@ -562,12 +562,12 @@ func ConfigureProvider(ctx context.Context, s *schema.ResourceData) (any, diag.D
providerCtx.EnabledFeatures = expandStringList(v.(*schema.Set).List())
}

if os.Getenv("TF_ACC") != "" && os.Getenv("SF_TF_ACC_TEST_ENABLE_ALL_PREVIEW_FEATURES") == "true" {
if oswrapper.Getenv("TF_ACC") != "" && oswrapper.Getenv("SF_TF_ACC_TEST_ENABLE_ALL_PREVIEW_FEATURES") == "true" {
providerCtx.EnabledFeatures = previewfeatures.AllPreviewFeatures
}

// needed for tests verifying different provider setups
if os.Getenv(resource.EnvTfAcc) != "" && os.Getenv(string(testenvs.ConfigureClientOnce)) == "true" {
if oswrapper.Getenv(resource.EnvTfAcc) != "" && oswrapper.Getenv(string(testenvs.ConfigureClientOnce)) == "true" {
configureProviderCtx = providerCtx
configureClientError = clientErr
} else {
Expand Down
27 changes: 27 additions & 0 deletions pkg/provider/provider_acceptance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,33 @@ func TestAcc_Provider_tomlConfig(t *testing.T) {
})
}

func TestAcc_Provider_tomlConfigIsTooBig(t *testing.T) {
_ = testenvs.GetOrSkipTest(t, testenvs.EnableAcceptance)
acc.TestAccPreCheck(t)
t.Setenv(string(testenvs.ConfigureClientOnce), "")

c := make([]byte, 11*1024*1024)
tomlConfig := acc.TestClient().StoreTempTomlConfig(t, func(profile string) string {
return string(c)
})

resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories,
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.RequireAbove(tfversion.Version1_5_0),
},
Steps: []resource.TestStep{
{
PreConfig: func() {
t.Setenv(snowflakeenvs.ConfigPath, tomlConfig.Path)
},
Config: config.FromModels(t, providermodel.SnowflakeProvider().WithProfile(tomlConfig.Path), datasourceModel()),
ExpectError: regexp.MustCompile(fmt.Sprintf("could not load config file: config file %s is too big - maximum allowed size is 10MB", tomlConfig.Path)),
},
},
})
}

func TestAcc_Provider_envConfig(t *testing.T) {
_ = testenvs.GetOrSkipTest(t, testenvs.EnableAcceptance)
acc.TestAccPreCheck(t)
Expand Down
12 changes: 6 additions & 6 deletions pkg/sdk/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import (
"log"
"net"
"net/url"
"os"
"path/filepath"
"slices"
"strings"
"time"

"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections"
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/oswrapper"
"github.com/pelletier/go-toml/v2"
"github.com/snowflakedb/gosnowflake"
"github.com/youmark/pkcs8"
Expand All @@ -36,7 +36,7 @@ func ProfileConfig(profile string) (*gosnowflake.Config, error) {
return nil, err
}

configs, err := loadConfigFile(path)
configs, err := LoadConfigFile(path)
if err != nil {
return nil, fmt.Errorf("could not load config file: %w", err)
}
Expand Down Expand Up @@ -199,12 +199,12 @@ func boolToConfigBool(v bool) gosnowflake.ConfigBool {

func GetConfigFileName() (string, error) {
// has the user overridden the default config path?
if configPath, ok := os.LookupEnv("SNOWFLAKE_CONFIG_PATH"); ok {
if configPath, ok := oswrapper.LookupEnv("SNOWFLAKE_CONFIG_PATH"); ok {
if configPath != "" {
return configPath, nil
}
}
dir, err := os.UserHomeDir()
dir, err := oswrapper.UserHomeDir()
if err != nil {
return "", err
}
Expand Down Expand Up @@ -360,8 +360,8 @@ func pointerUrlAttributeSet(src *string, dst **url.URL) error {
return nil
}

func loadConfigFile(path string) (map[string]ConfigDTO, error) {
dat, err := os.ReadFile(path)
func LoadConfigFile(path string) (map[string]ConfigDTO, error) {
dat, err := oswrapper.ReadFileSafe(path)
if err != nil {
return nil, err
}
Expand Down
8 changes: 4 additions & 4 deletions pkg/sdk/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func TestLoadConfigFile(t *testing.T) {
`
configPath := testhelpers.TestFile(t, "config", []byte(c))

m, err := loadConfigFile(configPath)
m, err := LoadConfigFile(configPath)
require.NoError(t, err)
assert.Equal(t, "TEST_ACCOUNT", *m["default"].AccountName)
assert.Equal(t, "TEST_ORG", *m["default"].OrganizationName)
Expand All @@ -58,7 +58,7 @@ func TestLoadConfigFileWithUnknownFields(t *testing.T) {
`
configPath := testhelpers.TestFile(t, "config", []byte(c))

m, err := loadConfigFile(configPath)
m, err := LoadConfigFile(configPath)
require.NoError(t, err)
assert.Equal(t, map[string]ConfigDTO{
"default": {
Expand All @@ -74,7 +74,7 @@ func TestLoadConfigFileWithInvalidFieldValue(t *testing.T) {
`
configPath := testhelpers.TestFile(t, "config", []byte(c))

_, err := loadConfigFile(configPath)
_, err := LoadConfigFile(configPath)
require.ErrorContains(t, err, "toml: cannot decode TOML integer into struct field sdk.ConfigDTO.AccountName of type *string")
}

Expand Down Expand Up @@ -193,7 +193,7 @@ func TestProfileConfig(t *testing.T) {
t.Setenv(snowflakeenvs.ConfigPath, filename)

config, err := ProfileConfig("orgadmin")
require.ErrorContains(t, err, fmt.Sprintf("could not load config file: open %s: no such file or directory", filename))
require.ErrorContains(t, err, fmt.Sprintf("could not load config file: reading information about the config file: stat %s: no such file or directory", filename))
require.Nil(t, config)
})
}
Expand Down
2 changes: 2 additions & 0 deletions templates/index.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ password='password'
role='ACCOUNTADMIN'
```

--> **Note: TOML file size is limited to 10MB.

Not all fields must be configured in one source; users can choose which fields are configured in which source.
Provider uses an established hierarchy of sources. The current behavior is that for each field:
1. Check if it is present in the provider configuration. If yes, use this value. If not, go to step 2.
Expand Down