diff --git a/docs/data-sources/current_account.md b/docs/data-sources/current_account.md new file mode 100644 index 0000000000..fd56e38cb3 --- /dev/null +++ b/docs/data-sources/current_account.md @@ -0,0 +1,38 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "snowflake_current_account Data Source - terraform-provider-snowflake" +subcategory: "" +description: |- + +--- + +# snowflake_current_account (Data Source) + + + +## Example Usage + +```terraform +data "snowflake_current_account" "this" {} + +resource "aws_ssm_parameter" "snowflake_account_url" { + name = "/snowflake/account_url" + type = "String" + value = data.snowflake_current_account.this.url +} +``` + + +## Schema + +### Optional + +- **id** (String) The ID of this resource. + +### Read-Only + +- **account** (String) The Snowflake Account ID; as returned by CURRENT_ACCOUNT(). +- **region** (String) The Snowflake Region; as returned by CURRENT_REGION() +- **url** (String) The Snowflake URL. + + diff --git a/examples/data-sources/snowflake_current_account/data-source.tf b/examples/data-sources/snowflake_current_account/data-source.tf new file mode 100644 index 0000000000..eff3a1746e --- /dev/null +++ b/examples/data-sources/snowflake_current_account/data-source.tf @@ -0,0 +1,7 @@ +data "snowflake_current_account" "this" {} + +resource "aws_ssm_parameter" "snowflake_account_url" { + name = "/snowflake/account_url" + type = "String" + value = data.snowflake_current_account.this.url +} diff --git a/pkg/datasources/current_account.go b/pkg/datasources/current_account.go new file mode 100644 index 0000000000..52b5b5de90 --- /dev/null +++ b/pkg/datasources/current_account.go @@ -0,0 +1,64 @@ +package datasources + +import ( + "database/sql" + "fmt" + "log" + + "github.com/chanzuckerberg/terraform-provider-snowflake/pkg/snowflake" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var currentAccountSchema = map[string]*schema.Schema{ + "account": { + Type: schema.TypeString, + Computed: true, + Description: "The Snowflake Account ID; as returned by CURRENT_ACCOUNT().", + }, + + "region": { + Type: schema.TypeString, + Computed: true, + Description: "The Snowflake Region; as returned by CURRENT_REGION()", + }, + + "url": { + Type: schema.TypeString, + Computed: true, + Description: "The Snowflake URL.", + }, +} + +// CurrentAccount the Snowflake current account resource +func CurrentAccount() *schema.Resource { + return &schema.Resource{ + Read: ReadCurrentAccount, + Schema: currentAccountSchema, + } +} + +// ReadCurrentAccount read the current snowflake account information +func ReadCurrentAccount(d *schema.ResourceData, meta interface{}) error { + db := meta.(*sql.DB) + acc, err := snowflake.ReadCurrentAccount(db) + + if err != nil { + log.Printf("[DEBUG] current_account failed to decode") + d.SetId("") + return nil + } + + d.SetId(fmt.Sprintf("%s.%s", acc.Account, acc.Region)) + d.Set("account", acc.Account) + d.Set("region", acc.Region) + url, err := acc.AccountURL() + + if err != nil { + log.Printf("[DEBUG] generating snowflake url failed") + d.SetId("") + return nil + } + + d.Set("url", url) + return nil +} diff --git a/pkg/datasources/current_account_acceptance_test.go b/pkg/datasources/current_account_acceptance_test.go new file mode 100644 index 0000000000..9b5438df46 --- /dev/null +++ b/pkg/datasources/current_account_acceptance_test.go @@ -0,0 +1,30 @@ +package datasources_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccCurrentAccount(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + Providers: providers(), + Steps: []resource.TestStep{ + { + Config: currentAccount(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("data.snowflake_current_account.p", "account"), + resource.TestCheckResourceAttrSet("data.snowflake_current_account.p", "region"), + resource.TestCheckResourceAttrSet("data.snowflake_current_account.p", "url"), + ), + }, + }, + }) +} + +func currentAccount() string { + s := ` + data snowflake_current_account p {} + ` + return s +} diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index c2ade9da7c..089805dcfb 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -190,6 +190,7 @@ func getResources() map[string]*schema.Resource { func getDataSources() map[string]*schema.Resource { dataSources := map[string]*schema.Resource{ + "snowflake_current_account": datasources.CurrentAccount(), "snowflake_system_generate_scim_access_token": datasources.SystemGenerateSCIMAccessToken(), "snowflake_system_get_aws_sns_iam_policy": datasources.SystemGetAWSSNSIAMPolicy(), "snowflake_system_get_privatelink_config": datasources.SystemGetPrivateLinkConfig(), diff --git a/pkg/snowflake/current_account.go b/pkg/snowflake/current_account.go new file mode 100644 index 0000000000..f9b5f821cc --- /dev/null +++ b/pkg/snowflake/current_account.go @@ -0,0 +1,68 @@ +package snowflake + +import ( + "database/sql" + "fmt" + "strings" + + "github.com/jmoiron/sqlx" +) + +// taken from https://docs.snowflake.com/en/user-guide/admin-account-identifier.html#snowflake-region-ids +var regionMapping = map[string]string{ + "aws_us_west_2": "", // left black as this is the default + "aws_us_east_2": "us-east-2.aws", + "aws_us_east_1": "us-east-1", + "aws_us_east_1_gov": "us-east-1-gov.aws", + "aws_ca_central_1": "ca-central-1.aws", + "aws_eu_west_1": "eu-west-1", + "aws_eu_west_2": "eu-west-2.aws", + "aws_eu_central_1": "eu-central-1", + "aws_ap_northeast_1": "ap-northeast-1.aws", + "aws_ap_south_1": "ap-south-1.aws", + "aws_ap_southeast_1": "ap-southeast-1", + "aws_ap_southeast_2": "ap-southeast-2", + "gcp_us_central1": "us-central1.gcp", + "gcp_europe_west2": "europe-west2.gcp", + "gcp_europe_west4": "europe-west4.gcp", + "azure_westus2": "west-us-2.azure", + "azure_eastus2": "east-us-2.azure", + "azure_usgovvirginia": "us-gov-virginia.azure", + "azure_canadacentral": "canada-central.azure", + "azure_westeurope": "west-europe.azure", + "azure_southeastasia": "southeast-asia.azure", + "azure_switzerlandnorth": "switzerland-north.azure", + "azure_australiaeast": "australia-east.azure", +} + +func SelectCurrentAccount() string { + return `SELECT CURRENT_ACCOUNT() AS "account", CURRENT_REGION() AS "region";` +} + +type account struct { + Account string `db:"account"` + Region string `db:"region"` +} + +func ScanCurrentAccount(row *sqlx.Row) (*account, error) { + acc := &account{} + err := row.StructScan(acc) + return acc, err +} + +func ReadCurrentAccount(db *sql.DB) (*account, error) { + row := QueryRow(db, SelectCurrentAccount()) + return ScanCurrentAccount(row) +} + +func (acc *account) AccountURL() (string, error) { + if region_id, ok := regionMapping[strings.ToLower(acc.Region)]; ok { + account_id := acc.Account + if len(region_id) > 0 { + account_id = fmt.Sprintf("%s.%s", account_id, region_id) + } + return fmt.Sprintf("https://%s.snowflakecomputing.com", account_id), nil + } + + return "", fmt.Errorf("Failed to map Snowflake account region %s to a region_id", acc.Region) +} diff --git a/pkg/snowflake/current_account_test.go b/pkg/snowflake/current_account_test.go new file mode 100644 index 0000000000..082e73eb64 --- /dev/null +++ b/pkg/snowflake/current_account_test.go @@ -0,0 +1,71 @@ +package snowflake_test + +import ( + sqlmock "github.com/DATA-DOG/go-sqlmock" + "github.com/chanzuckerberg/terraform-provider-snowflake/pkg/snowflake" + "github.com/jmoiron/sqlx" + "github.com/stretchr/testify/require" + "testing" +) + +func TestCurrentAccountSelect(t *testing.T) { + r := require.New(t) + r.Equal(`SELECT CURRENT_ACCOUNT() AS "account", CURRENT_REGION() AS "region";`, snowflake.SelectCurrentAccount()) +} + +func TestCurrentAccountRead(t *testing.T) { + type testCaseEntry struct { + account string + region string + url string + } + + testCases := map[string]testCaseEntry{ + "aws oregon": { + "ab1234", + "AWS_US_WEST_2", + "https://ab1234.snowflakecomputing.com", + }, + "aws n virginia": { + "cd5678", + "AWS_US_EAST_1", + "https://cd5678.us-east-1.snowflakecomputing.com", + }, + "aws canada central": { + "ef9012", + "AWS_CA_CENTRAL_1", + "https://ef9012.ca-central-1.aws.snowflakecomputing.com", + }, + "gcp canada central": { + "gh3456", + "gcp_us_central1", + "https://gh3456.us-central1.gcp.snowflakecomputing.com", + }, + "azure washington": { + "ij7890", + "azure_westus2", + "https://ij7890.west-us-2.azure.snowflakecomputing.com", + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + r := require.New(t) + mockDB, mock, err := sqlmock.New() + r.NoError(err) + defer mockDB.Close() + sqlxDB := sqlx.NewDb(mockDB, "sqlmock") + + rows := sqlmock.NewRows([]string{"account", "region"}).AddRow(testCase.account, testCase.region) + mock.ExpectQuery(`SELECT CURRENT_ACCOUNT\(\) AS "account", CURRENT_REGION\(\) AS "region";`).WillReturnRows(rows) + + acc, err := snowflake.ReadCurrentAccount(sqlxDB.DB) + r.NoError(err) + r.Equal(testCase.account, acc.Account) + r.Equal(testCase.region, acc.Region) + url, err := acc.AccountURL() + r.NoError(err) + r.Equal(testCase.url, url) + }) + } +}