-
Notifications
You must be signed in to change notification settings - Fork 427
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: Fix provider config hierarchy #2551
Changes from all commits
cf186e0
7b9a30d
13d0b10
7b92757
40a72e2
a273d2f
f68b994
d2727c5
7078688
a1e0409
2c3bb13
c4eec46
50bbfa7
50772f8
a2e4994
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package testenvs | ||
|
||
import ( | ||
"os" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func AssertEnvNotSet(t *testing.T, envName string) { | ||
t.Helper() | ||
require.Emptyf(t, os.Getenv(envName), "environment variable %v should not be set", envName) | ||
} | ||
|
||
func AssertEnvSet(t *testing.T, envName string) { | ||
t.Helper() | ||
require.NotEmptyf(t, os.Getenv(envName), "environment variable %v should not be empty", envName) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package testenvs_test | ||
|
||
import ( | ||
"sync" | ||
"testing" | ||
|
||
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/testenvs" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func Test_GetOrSkipTest(t *testing.T) { | ||
// runGetOrSkipInGoroutineAndWaitForCompletion is needed because underneath we test t.Skipf, that leads to t.SkipNow() that in turn call runtime.Goexit() | ||
// so we need to be wrapped in a Goroutine. | ||
runGetOrSkipInGoroutineAndWaitForCompletion := func(t *testing.T) string { | ||
t.Helper() | ||
var env string | ||
var wg sync.WaitGroup | ||
wg.Add(1) | ||
go func() { | ||
defer wg.Done() | ||
env = testenvs.GetOrSkipTest(t, testenvs.User) | ||
}() | ||
wg.Wait() | ||
return env | ||
} | ||
|
||
t.Run("skip test if missing", func(t *testing.T) { | ||
t.Setenv(string(testenvs.User), "") | ||
|
||
tut := &testing.T{} | ||
env := runGetOrSkipInGoroutineAndWaitForCompletion(tut) | ||
|
||
require.True(t, tut.Skipped()) | ||
require.Empty(t, env) | ||
}) | ||
|
||
t.Run("get env if exists", func(t *testing.T) { | ||
t.Setenv(string(testenvs.User), "user") | ||
|
||
tut := &testing.T{} | ||
env := runGetOrSkipInGoroutineAndWaitForCompletion(tut) | ||
|
||
require.False(t, tut.Skipped()) | ||
require.Equal(t, "user", env) | ||
}) | ||
} | ||
|
||
func Test_Assertions(t *testing.T) { | ||
// runAssertionInGoroutineAndWaitForCompletion is needed because underneath we test require, that leads to t.FailNow() that in turn call runtime.Goexit() | ||
// so we need to be wrapped in a Goroutine. | ||
runAssertionInGoroutineAndWaitForCompletion := func(assertion func()) { | ||
var wg sync.WaitGroup | ||
wg.Add(1) | ||
go func() { | ||
defer wg.Done() | ||
assertion() | ||
}() | ||
wg.Wait() | ||
} | ||
|
||
t.Run("test if env does not exist", func(t *testing.T) { | ||
t.Setenv(string(testenvs.User), "") | ||
|
||
tut1 := &testing.T{} | ||
runAssertionInGoroutineAndWaitForCompletion(func() { testenvs.AssertEnvNotSet(tut1, string(testenvs.User)) }) | ||
|
||
tut2 := &testing.T{} | ||
runAssertionInGoroutineAndWaitForCompletion(func() { testenvs.AssertEnvSet(tut2, string(testenvs.User)) }) | ||
|
||
require.False(t, tut1.Failed()) | ||
require.True(t, tut2.Failed()) | ||
}) | ||
|
||
t.Run("test if env exists", func(t *testing.T) { | ||
t.Setenv(string(testenvs.User), "user") | ||
|
||
tut1 := &testing.T{} | ||
runAssertionInGoroutineAndWaitForCompletion(func() { testenvs.AssertEnvNotSet(tut1, string(testenvs.User)) }) | ||
|
||
tut2 := &testing.T{} | ||
runAssertionInGoroutineAndWaitForCompletion(func() { testenvs.AssertEnvSet(tut2, string(testenvs.User)) }) | ||
|
||
require.True(t, tut1.Failed()) | ||
require.False(t, tut2.Failed()) | ||
}) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package testenvs | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"testing" | ||
) | ||
|
||
type env string | ||
|
||
const ( | ||
User env = "TEST_SF_TF_USER" | ||
Password env = "TEST_SF_TF_PASSWORD" // #nosec G101 | ||
Account env = "TEST_SF_TF_ACCOUNT" | ||
Role env = "TEST_SF_TF_ROLE" | ||
Host env = "TEST_SF_TF_HOST" | ||
) | ||
|
||
func GetOrSkipTest(t *testing.T, envName Env) string { | ||
t.Helper() | ||
env := os.Getenv(fmt.Sprintf("%v", envName)) | ||
if env == "" { | ||
t.Skipf("Skipping %s, env %v missing", t.Name(), envName) | ||
} | ||
return env | ||
} | ||
|
||
type Env interface { | ||
xxxProtected() | ||
} | ||
|
||
func (e env) xxxProtected() {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package testprofiles | ||
|
||
const ( | ||
Default = "default" | ||
Secondary = "secondary_test_account" | ||
IncorrectUserAndPassword = "incorrect_test_profile" | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package snowflakeenvs | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should this not be under acceptance package? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. acceptance package is used for tests only whereas this one can be used for production code too. |
||
|
||
const ( | ||
Account = "SNOWFLAKE_ACCOUNT" | ||
User = "SNOWFLAKE_USER" | ||
Password = "SNOWFLAKE_PASSWORD" | ||
Role = "SNOWFLAKE_ROLE" | ||
ConfigPath = "SNOWFLAKE_CONFIG_PATH" | ||
Host = "SNOWFLAKE_HOST" | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
package provider_test | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"regexp" | ||
"testing" | ||
|
||
acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" | ||
|
||
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/testenvs" | ||
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/testprofiles" | ||
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/snowflakeenvs" | ||
"github.com/hashicorp/terraform-plugin-testing/helper/resource" | ||
"github.com/hashicorp/terraform-plugin-testing/tfversion" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestAcc_Provider_configHierarchy(t *testing.T) { | ||
user := testenvs.GetOrSkipTest(t, testenvs.User) | ||
pass := testenvs.GetOrSkipTest(t, testenvs.Password) | ||
account := testenvs.GetOrSkipTest(t, testenvs.Account) | ||
role := testenvs.GetOrSkipTest(t, testenvs.Role) | ||
host := testenvs.GetOrSkipTest(t, testenvs.Host) | ||
|
||
nonExistingUser := "non-existing-user" | ||
|
||
resource.Test(t, resource.TestCase{ | ||
ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, | ||
PreCheck: func() { | ||
acc.TestAccPreCheck(t) | ||
testenvs.AssertEnvNotSet(t, snowflakeenvs.User) | ||
testenvs.AssertEnvNotSet(t, snowflakeenvs.Password) | ||
}, | ||
TerraformVersionChecks: []tfversion.TerraformVersionCheck{ | ||
tfversion.RequireAbove(tfversion.Version1_5_0), | ||
}, | ||
Steps: []resource.TestStep{ | ||
// make sure that we fail for incorrect profile | ||
{ | ||
Config: providerConfig(testprofiles.IncorrectUserAndPassword), | ||
ExpectError: regexp.MustCompile("Incorrect username or password was specified"), | ||
}, | ||
// incorrect user in provider config should not be rewritten by profile and cause error | ||
{ | ||
Config: providerConfigWithUser(nonExistingUser, testprofiles.Default), | ||
ExpectError: regexp.MustCompile("Incorrect username or password was specified"), | ||
}, | ||
// correct user and password in provider's config should not be rewritten by a faulty config | ||
{ | ||
Config: providerConfigWithUserAndPassword(user, pass, testprofiles.IncorrectUserAndPassword), | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttr("data.snowflake_database.t", "name", acc.TestDatabaseName), | ||
), | ||
}, | ||
// incorrect user in env variable should not be rewritten by profile and cause error | ||
{ | ||
PreConfig: func() { | ||
t.Setenv(snowflakeenvs.User, nonExistingUser) | ||
}, | ||
Config: providerConfig(testprofiles.Default), | ||
ExpectError: regexp.MustCompile("Incorrect username or password was specified"), | ||
}, | ||
// correct user and password in env should not be rewritten by a faulty config | ||
{ | ||
PreConfig: func() { | ||
t.Setenv(snowflakeenvs.User, user) | ||
t.Setenv(snowflakeenvs.Password, pass) | ||
}, | ||
Config: providerConfig(testprofiles.IncorrectUserAndPassword), | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttr("data.snowflake_database.t", "name", acc.TestDatabaseName), | ||
), | ||
}, | ||
// user on provider level wins (it's incorrect - env and profile ones are) | ||
{ | ||
Config: providerConfigWithUser(nonExistingUser, testprofiles.Default), | ||
ExpectError: regexp.MustCompile("Incorrect username or password was specified"), | ||
}, | ||
// there is no config (by setting the dir to something different than .snowflake/config) | ||
{ | ||
PreConfig: func() { | ||
dir, err := os.UserHomeDir() | ||
require.NoError(t, err) | ||
t.Setenv(snowflakeenvs.ConfigPath, dir) | ||
}, | ||
Config: providerConfigWithUserAndPassword(user, pass, testprofiles.Default), | ||
ExpectError: regexp.MustCompile("account is empty"), | ||
}, | ||
// provider's config should not be rewritten by env when there is no profile (incorrect user in config versus correct one in env) - proves #2242 | ||
{ | ||
PreConfig: func() { | ||
testenvs.AssertEnvSet(t, snowflakeenvs.ConfigPath) | ||
t.Setenv(snowflakeenvs.User, user) | ||
t.Setenv(snowflakeenvs.Password, pass) | ||
t.Setenv(snowflakeenvs.Account, account) | ||
t.Setenv(snowflakeenvs.Role, role) | ||
t.Setenv(snowflakeenvs.Host, host) | ||
}, | ||
Config: providerConfigWithUser(nonExistingUser, testprofiles.Default), | ||
ExpectError: regexp.MustCompile("Incorrect username or password was specified"), | ||
}, | ||
// make sure the teardown is fine by using a correct env config at the end | ||
{ | ||
PreConfig: func() { | ||
testenvs.AssertEnvSet(t, snowflakeenvs.ConfigPath) | ||
testenvs.AssertEnvSet(t, snowflakeenvs.User) | ||
testenvs.AssertEnvSet(t, snowflakeenvs.Password) | ||
testenvs.AssertEnvSet(t, snowflakeenvs.Account) | ||
testenvs.AssertEnvSet(t, snowflakeenvs.Role) | ||
testenvs.AssertEnvSet(t, snowflakeenvs.Host) | ||
}, | ||
Config: emptyProviderConfig(), | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttr("data.snowflake_database.t", "name", acc.TestDatabaseName), | ||
), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func emptyProviderConfig() string { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. shouldnt we be putting these in a testdata directory? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We did not agree to automatically move every config to a file - it is not necessarily more readable. Currently, we accept both options (they are both not ideal), but we have ideas for the better, third option (maybe coming soon). |
||
return ` | ||
provider "snowflake" { | ||
}` + datasourceConfig() | ||
} | ||
|
||
func providerConfig(profile string) string { | ||
return fmt.Sprintf(` | ||
provider "snowflake" { | ||
profile = "%[1]s" | ||
} | ||
`, profile) + datasourceConfig() | ||
} | ||
|
||
func providerConfigWithUser(user string, profile string) string { | ||
return fmt.Sprintf(` | ||
provider "snowflake" { | ||
user = "%[1]s" | ||
profile = "%[2]s" | ||
} | ||
`, user, profile) + datasourceConfig() | ||
} | ||
|
||
func providerConfigWithUserAndPassword(user string, pass string, profile string) string { | ||
return fmt.Sprintf(` | ||
provider "snowflake" { | ||
user = "%[1]s" | ||
password = "%[2]s" | ||
profile = "%[3]s" | ||
} | ||
`, user, pass, profile) + datasourceConfig() | ||
} | ||
|
||
func datasourceConfig() string { | ||
return fmt.Sprintf(` | ||
data snowflake_database "t" { | ||
name = "%s" | ||
}`, acc.TestDatabaseName) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this interface necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not necessary but it's fun. It prevents you from calling the
GetOrSkipTest
with anything else than listed env vars (on compilation level).